Cyber-Physical Systems Series

Embedded Systems and Real-Time Operation

The computers inside cyber-physical systems, how they reach the physical world, and how to guarantee they act in time.

What Makes a System Embedded?

An embedded system is a computer built into a larger device to perform a dedicated function. Users rarely see it as a computer. It runs the engine, the brakes, the thermostat, or the infusion pump. In a cyber-physical system, the embedded computer is the "cyber" half of the loop introduced in Introduction to Cyber-Physical Systems.

What separates embedded design from general-purpose programming is a set of constraints that are all active at the same time:

ConstraintWhat it means in practice
TimingResults must be produced before a deadline set by physics, not by user patience
ResourcesKilobytes to megabytes of RAM, modest clock rates, often no memory management unit or file system
Power and energyBattery or energy-harvesting operation; the processor sleeps most of the time and wakes on events
ReliabilityNo one is there to reboot it; failures may be dangerous or expensive
LifetimeDeployed for 10 to 30 years, outliving its toolchain, its developers, and sometimes its manufacturer
CostProduced in volumes where a few cents per unit matters

Computing platforms

Microcontroller (MCU)

Processor, memory, timers, and peripherals on one chip (for example ARM Cortex-M, RISC-V, ESP32, RP2040). Usually bare-metal or a small RTOS. Simple pipelines give fairly predictable timing.

Application processor / SoC

Multicore chips with caches and a memory management unit that run Linux (for example Cortex-A on a Raspberry Pi). Much more capable, but timing is much harder to bound.

Digital signal processor (DSP)

Optimized for multiply-accumulate loops in filtering, audio, radio, and motor control.

FPGA

Reconfigurable hardware. Logic runs in true parallel with cycle-exact timing, at the cost of harder development.

GPU / NPU accelerators

Run perception and machine-learning workloads in vehicles and robots. They have high throughput but are notoriously difficult to analyze for worst-case timing.

Programmable logic controller (PLC)

Ruggedized industrial computers with a fixed scan cycle. Covered in Control Systems and Industrial Applications.

The Hardware/Software Interface

Software reaches the physical world through peripherals: hardware blocks that the processor controls by reading and writing special addresses called memory-mapped registers. Writing a bit to a register can start a motor; reading one can return a sensor value.

Sensing: sampling and quantization

An analog-to-digital converter (ADC) measures a voltage at discrete instants (sampling) and rounds it to one of 2n levels (quantization). A 12-bit ADC on a 3.3 V range resolves about 0.8 mV.

Sampling too slowly causes aliasing: a fast signal looks like a slow one. The Nyquist rule (sample faster than twice the highest frequency present) is the minimum, and control loops usually sample 10 to 30 times faster than the plant's dynamics.

Actuation: PWM and DACs

Most actuators are driven by pulse-width modulation (PWM): a digital pin switches on and off quickly, and the fraction of time it is on (the duty cycle) sets the average power delivered to a motor, heater, or LED.

A digital-to-analog converter (DAC) produces a true analog voltage when one is needed. Either way, the physical actuator then has its own dynamics, saturation limits, and delay.

Getting data in and out: polling, interrupts, and DMA

MechanismHow it worksStrengthWeakness
PollingSoftware repeatedly checks a status registerSimple, timing is easy to reason aboutWastes cycles; can miss short events between checks
InterruptsHardware signals the processor, which pauses current work and runs an interrupt service routine (ISR)Fast response; the processor can sleep until neededPreempts other code at unpredictable points; shared data needs protection
DMAA direct memory access controller moves data between peripherals and memory without the processorHigh throughput with little CPU loadCompetes with the CPU for the memory bus, which adds timing interference
Interrupt hazard: if an ISR and the main program both update a multi-byte variable, the main program can read half of an old value and half of a new one. Keep ISRs short, make shared updates atomic (or briefly disable interrupts), and mark shared variables so the compiler does not cache them in registers (volatile in C).

Common buses and networks

BusTypical useTiming character
UARTDebug consoles, GPS modulesPoint-to-point, asynchronous
I²CLow-speed sensors on one boardShared two-wire bus with addressing; slow
SPIFast sensors, displays, flash memoryFull-duplex, clocked by the controller; fast and predictable
CAN / CAN FDAutomotive and industrial control networksPriority-based arbitration by message ID, which makes worst-case latency analyzable
EtherCAT, PROFINETFactory automationDeterministic industrial Ethernet with cycle times in microseconds to milliseconds
Time-Sensitive Networking (TSN)Converged vehicle and industrial EthernetIEEE 802.1 standards for clock synchronization and scheduled traffic with bounded latency

What "Real-Time" Really Means

A real-time system is one whose correctness depends on when results are produced as well as what they are. Real-time does not mean fast. A system that responds in 10 ms every time is more real-time than one that responds in 1 ms on average and 200 ms occasionally. The goal is predictability.

ClassConsequence of a missed deadlineExamples
HardFailure, possibly catastrophic; a late result is a wrong resultAirbag deployment, flight control surfaces, pacemaker pacing
FirmA late result is useless and discarded, but occasional misses are tolerableVideo frame decoding, a sensor sample used only if fresh
SoftValue degrades with lateness; quality of service suffersInfotainment, telemetry logging, user interfaces

Timing vocabulary

Release time: when a job becomes ready to run.

Deadline: when it must be finished, often measured relative to the release.

Response time: finish time minus release time. The worst-case response time is what must fit within the deadline.

Latency: delay from an event (a sensor edge, an interrupt) until the system reacts.

Jitter: variation in latency or period from one cycle to the next. Control loops are sensitive to jitter, not just to average delay.

WCET: worst-case execution time, the longest a piece of code can take on a given processor in isolation. Every schedulability guarantee rests on it.

Why execution time varies

The same code on the same input can take different amounts of time from run to run. Each item below is a source of timing variability that a WCET bound has to cover:

WCET can be estimated by measurement (run many times and add a safety margin, which is never a proof) or bounded by static analysis of the binary against a model of the processor (sound, but pessimistic and only possible for analyzable hardware). Safety-critical systems often disable caches or pin tasks to cores just to make timing analyzable.

The Periodic Task Model

Real-time scheduling theory abstracts software into tasks. Each periodic task τi releases a new job every Ti time units, and each job needs up to Ci units of processor time (its WCET) before a relative deadline Di. In the simplest and most common case, Di = Ti: each job must finish before the next one is released.

Utilization

The fraction of processor time a task set demands:

U = Σ Ci / Ti

If U > 1, no scheduler on one processor can meet every deadline. If U ≤ 1, whether deadlines are met depends on the scheduling policy.

Hyperperiod

The least common multiple of all periods. The schedule repeats after every hyperperiod, so simulating one is enough to check a periodic task set.

Sporadic tasks have a minimum gap between releases instead of a fixed period. Aperiodic tasks arrive unpredictably and are usually handled by a reserved server budget.

Real-Time Scheduling

Cyclic executive

A precomputed table says exactly which function runs in each time slot. It is fully deterministic and easy to certify, which is why it is common in avionics. It is also brittle: changing one task can mean rebuilding the whole table.

Fixed priority (RM, DM)

Each task gets a static priority, and the highest-priority ready job always runs. Rate monotonic (RM) gives higher priority to shorter periods; deadline monotonic (DM) to shorter deadlines. Both are optimal among fixed-priority policies for their task models. Fixed priority is what nearly every RTOS implements.

Earliest deadline first (EDF)

Priorities are dynamic: the job with the nearest absolute deadline runs. On one processor with D = T, EDF meets every deadline whenever U ≤ 1, so it is optimal. In exchange, its overload behavior is harder to predict.

Schedulability tests

A schedulability test decides, before deployment, whether a task set will always meet its deadlines. The table below lists the classic tests for one processor with independent tasks and D = T.

PolicyTestType
EDFU ≤ 1Exact (necessary and sufficient)
RMU ≤ n(21/n − 1), which falls toward ln 2 ≈ 0.693 as n grows (Liu & Layland bound)Sufficient only; failing it is inconclusive
Any fixed priorityResponse-time analysis: iterate Ri = Ci + Σj ∈ hp(i) ⌈Ri / Tj⌉ Cj until it converges; schedulable if every Ri ≤ DiExact for this task model

Response-time analysis says that task i's worst case happens when every higher-priority task j releases at the same instant (the critical instant). Then i must wait for its own Ci plus every higher-priority job released while it is still waiting.

Example: Schedulability analysis

The code below applies all three tests to two task sets. Each task is (name, C, T) with D = T, and RM priorities are assigned by period.

import math

def utilization(tasks):
    return sum(c / t for _, c, t in tasks)

def liu_layland_bound(n):
    return n * (2 ** (1 / n) - 1)

def response_times(tasks):
    ordered = sorted(tasks, key=lambda task: task[2])
    results = []
    for i, (name, c, t) in enumerate(ordered):
        higher = ordered[:i]
        r = c + sum(hc for _, hc, _ in higher)
        while True:
            nxt = c + sum(math.ceil(r / ht) * hc for _, hc, ht in higher)
            if nxt == r or nxt > t:
                break
            r = nxt
        results.append((name, nxt, t, nxt <= t))
    return results

def analyze(label, tasks):
    u = utilization(tasks)
    bound = liu_layland_bound(len(tasks))
    print(f'{label}: U = {u:.3f}, RM bound = {bound:.3f}, EDF bound = 1.000')
    print(f'  RM utilization test: {"pass" if u <= bound else "inconclusive"}')
    print(f'  EDF utilization test: {"pass" if u <= 1 else "fail"}')
    for name, r, t, ok in response_times(tasks):
        print(f'  RM response time {name:<10} R = {r:>2}  deadline = {t:>2}  {"ok" if ok else "MISS"}')

analyze('Set A', [('imu', 1, 4), ('control', 2, 6), ('logger', 3, 12)])
analyze('Set B', [('control', 2, 5), ('vision', 4, 7)])

Output:

Set A: U = 0.833, RM bound = 0.780, EDF bound = 1.000
  RM utilization test: inconclusive
  EDF utilization test: pass
  RM response time imu        R =  1  deadline =  4  ok
  RM response time control    R =  3  deadline =  6  ok
  RM response time logger     R = 10  deadline = 12  ok
Set B: U = 0.971, RM bound = 0.828, EDF bound = 1.000
  RM utilization test: inconclusive
  EDF utilization test: pass
  RM response time control    R =  2  deadline =  5  ok
  RM response time vision     R =  8  deadline =  7  MISS

What to notice

  • Set A exceeds the Liu & Layland bound, yet response-time analysis proves it schedulable under RM. The bound is a quick sufficient check, not a verdict.
  • Set B fails under RM: the vision task's worst-case response time (8) exceeds its deadline (7). EDF can still schedule it because U ≤ 1.
  • All of this depends on C being a true upper bound. An underestimated WCET silently invalidates every guarantee.

Example: Watching RM and EDF schedule the same tasks

This discrete-time simulator runs Set B for one hyperperiod (35 time units). Each character is one time unit: c for control, v for vision, . for idle. A job still unfinished at its deadline is counted as a miss and dropped.

from math import lcm

def simulate(tasks, policy):
    horizon = lcm(*(t for _, _, t in tasks))
    jobs, timeline, misses = [], [], []
    for now in range(horizon):
        for name, c, t in tasks:
            if now % t == 0:
                jobs.append({'name': name, 'left': c, 'deadline': now + t, 'period': t})
        for job in [j for j in jobs if j['deadline'] <= now]:
            misses.append((job['name'], job['deadline']))
            jobs.remove(job)
        if not jobs:
            timeline.append('.')
            continue
        key = (lambda j: j['period']) if policy == 'RM' else (lambda j: j['deadline'])
        job = min(jobs, key=key)
        job['left'] -= 1
        timeline.append(job['name'][0])
        if job['left'] == 0:
            jobs.remove(job)
    return ''.join(timeline), misses

tasks = [('control', 2, 5), ('vision', 4, 7)]
for policy in ('RM', 'EDF'):
    timeline, misses = simulate(tasks, policy)
    print(f'{policy:<3} {timeline}')
    print(f'    misses: {misses if misses else "none"}')

Output:

RM  ccvvvccvvvccv.vccvvvccvvvccvvvccvv.
    misses: [('vision', 7)]
EDF ccvvvvccvvvvccvccvvvccvvvvccvvvvcc.
    misses: none

What to notice

  • At t = 5, RM preempts vision for the second control job because control always has the higher priority. Vision has 1 unit left at its deadline at t = 7, and misses.
  • At t = 5, EDF instead lets vision finish, because vision's deadline (7) is earlier than the new control job's deadline (10). Control still finishes by t = 8, well before 10.
  • The same processor, tasks, and total work produce a different physical outcome. The only thing that changed is the policy.

Shared Resources and Priority Inversion

Tasks share data, buses, and devices, usually protected by a mutex. Locking breaks the simple rule that the highest-priority ready task runs. A high-priority task can be forced to wait for a low-priority task that holds a lock it needs. A short wait of that kind is bounded blocking and is unavoidable.

Unbounded priority inversion happens when medium-priority tasks, which do not need the lock at all, preempt the low-priority lock holder. The high-priority task then waits for all of them as well.

Case study: Mars Pathfinder, 1997

After landing, the Pathfinder lander began resetting itself repeatedly. A low-priority meteorological data task held a mutex inside the VxWorks inter-process communication mechanism, and medium-priority tasks kept preempting it. A higher-priority bus distribution task blocked on that mutex and missed its cycle. The highest-priority scheduler task detected the missed cycle and reset the system. Engineers reproduced the problem on a ground replica, enabled priority inheritance on the mutex by changing a configuration setting, and uploaded the fix to Mars. (Glenn Reeves' account of the fix)

Priority inheritance protocol (PIP)

While a task holds a lock that a higher-priority task is waiting for, it temporarily runs at the waiter's priority. Medium-priority tasks can no longer preempt it. This is widely supported, for example in POSIX mutexes with PTHREAD_PRIO_INHERIT and in FreeRTOS mutexes.

Priority ceiling protocols (PCP, SRP)

Each lock gets a ceiling equal to the highest priority of any task that uses it. A task's access is restricted by those ceilings, which prevents deadlock and limits blocking to at most one critical section. That bound can be added directly to response-time analysis.

Example: Priority inversion with and without inheritance

Three jobs: L (low) locks a shared resource for 4 units, M (medium) needs 6 units of CPU but no lock, and H (high) needs the lock for 2 units. Each segment is ('cpu', n) or ('cs', n) for a critical section. Each character in the timeline is the job running in that time unit.

def run(jobs, inheritance, horizon=20):
    state = {n: {'seg': 0, 'left': j['segs'][0][1], 'done': None} for n, j in jobs.items()}
    owner, timeline = None, []
    for now in range(horizon):
        ready = [n for n, j in jobs.items() if j['release'] <= now and state[n]['done'] is None]
        def wants_lock(n):
            return jobs[n]['segs'][state[n]['seg']][0] == 'cs'
        def blocked(n):
            return wants_lock(n) and owner not in (None, n)
        def priority(n):
            p = jobs[n]['prio']
            if inheritance and n == owner:
                waiting = [jobs[w]['prio'] for w in ready if blocked(w)]
                p = max([p] + waiting)
            return p
        runnable = [n for n in ready if not blocked(n)]
        if not runnable:
            timeline.append('.')
            continue
        n = max(runnable, key=priority)
        if wants_lock(n):
            owner = n
        s = state[n]
        s['left'] -= 1
        timeline.append(n)
        if s['left'] == 0:
            if wants_lock(n):
                owner = None
            s['seg'] += 1
            if s['seg'] == len(jobs[n]['segs']):
                s['done'] = now + 1
            else:
                s['left'] = jobs[n]['segs'][s['seg']][1]
    return ''.join(timeline), {n: s['done'] - jobs[n]['release'] for n, s in state.items()}

jobs = {
    'L': {'prio': 1, 'release': 0, 'segs': [('cpu', 1), ('cs', 4), ('cpu', 1)]},
    'M': {'prio': 2, 'release': 2, 'segs': [('cpu', 6)]},
    'H': {'prio': 3, 'release': 3, 'segs': [('cpu', 1), ('cs', 2), ('cpu', 1)]},
}
for inheritance in (False, True):
    timeline, response = run(jobs, inheritance)
    label = 'with inheritance   ' if inheritance else 'without inheritance'
    print(f'{label} {timeline}  H response time = {response["H"]}')

Output:

without inheritance LLMHMMMMMLLLHHHL....  H response time = 12
with inheritance    LLMHLLLHHHMMMMML....  H response time = 7

What to notice

  • Without inheritance, H becomes blocked at t = 4, and then M runs for five straight units while L, which holds the lock, cannot run. H's response time grows to 12, and it would grow without limit if there were more medium tasks.
  • With inheritance, L runs at H's priority from t = 4, releases the lock at t = 7, and H finishes at t = 10. M is delayed, which is correct, because M has lower priority than H.
  • The total work is identical in both runs. Only the order changed.

Software Platforms

ApproachDescriptionWhen it fits
Bare-metal super-loopOne infinite loop polls inputs and calls functions in order, with interrupts for urgent eventsSmall, simple devices; easy to analyze, hard to extend
RTOSA small kernel provides preemptive fixed-priority scheduling, mutexes with priority inheritance, queues, and timers (for example FreeRTOS, Zephyr, VxWorks, QNX, INTEGRITY)Most microcontroller-based CPS with several concurrent activities
Real-time LinuxThe PREEMPT_RT patches make most of the Linux kernel preemptible. They have been part of the mainline kernel since version 6.12 (late 2024).Robots, gateways, and industrial PCs that need Linux's ecosystem with millisecond-to-microsecond latency targets
Hypervisor / partitioningIsolated partitions with guaranteed time and memory budgets (for example ARINC 653 in avionics)Mixed-criticality systems combining certified and uncertified software on one processor

Where Python fits

Standard CPython on a desktop operating system is not a hard real-time platform. The OS scheduler, garbage collection, and the interpreter itself add unbounded delays. Python is still valuable in CPS work: for analysis and simulation (like the examples on this page), for supervisory layers above a real-time core, and on microcontrollers through MicroPython, which offers direct register, timer, and interrupt access for prototyping.

One technique that matters in any language is how a periodic loop waits. Sleeping for a fixed period after doing the work lets every cycle's execution time and wake-up delay accumulate into drift. Sleeping until an absolute release time keeps the long-run rate exact, so only per-cycle jitter remains.

Example: Drift versus jitter in a periodic loop

import statistics
import time

def naive(period, cycles):
    start = time.perf_counter()
    stamps = []
    for _ in range(cycles):
        stamps.append(time.perf_counter())
        sum(i * i for i in range(2000))
        time.sleep(period)
    return start, stamps

def absolute(period, cycles):
    start = time.perf_counter()
    stamps = []
    for k in range(cycles):
        stamps.append(time.perf_counter())
        sum(i * i for i in range(2000))
        delay = start + (k + 1) * period - time.perf_counter()
        if delay > 0:
            time.sleep(delay)
    return start, stamps

def report(label, period, start, stamps):
    ideal = [start + k * period for k in range(len(stamps))]
    lateness = [(s - i) * 1e3 for s, i in zip(stamps, ideal)]
    gaps = [(b - a) * 1e3 for a, b in zip(stamps, stamps[1:])]
    print(f'{label:<9} mean period {statistics.mean(gaps):6.3f} ms  '
          f'jitter (stdev) {statistics.stdev(gaps):5.3f} ms  '
          f'drift after {len(stamps)} cycles {lateness[-1]:7.2f} ms')

period, cycles = 0.005, 400
report('naive', period, *naive(period, cycles))
report('absolute', period, *absolute(period, cycles))

Sample output (your numbers will differ by machine and load):

naive     mean period  5.247 ms  jitter (stdev) 0.111 ms  drift after 400 cycles   98.39 ms
absolute  mean period  5.001 ms  jitter (stdev) 0.210 ms  drift after 400 cycles    0.23 ms

What to notice

  • The naive loop's period is about 5% too long, so after 400 cycles (2 seconds) it is about 100 ms behind schedule. A controller designed for a 5 ms period is effectively running at a different rate.
  • The absolute-time loop has essentially no drift. Each cycle absorbs the previous cycle's delay.
  • Jitter remains in both cases. Removing it requires a real-time kernel, hardware timers, or isolated cores, which is exactly what an RTOS provides.

Beyond a Single Processor

Multicore scheduling

Partitioned scheduling assigns each task to one core and runs a uniprocessor scheduler on each. It is simple and analyzable, but assigning tasks to cores is a bin-packing problem. Global scheduling lets tasks migrate between cores. It has better average utilization, but the uniprocessor optimality results no longer hold, and interference through shared caches and memory complicates WCET.

Mixed criticality

Modern platforms combine functions of different criticality, such as braking control and infotainment, on shared hardware. Mixed-criticality scheduling guarantees high-criticality tasks under pessimistic WCET assumptions and drops or degrades low-criticality tasks if those assumptions are ever reached.

Time-triggered and logical-time approaches

Instead of reacting to events whenever they happen, time-triggered architectures act at globally synchronized instants. Logical execution time and reactor-based languages such as Lingua Franca separate the timing a program specifies from how long the hardware takes. These ideas return in System Design and Modeling.

Distributed timing

When sensing, computing, and actuation are spread across networked nodes, end-to-end latency includes network delay and clock offsets. Clock synchronization (IEEE 1588 PTP) and scheduled networks (TSN) extend real-time guarantees across a vehicle or a factory floor.

A Practical Real-Time Design Checklist

  1. Derive each deadline and period from the physics (plant dynamics, safety margins), not from convenience.
  2. Bound the WCET of every task, and state how it was obtained (measurement with margin, or static analysis).
  3. Choose a scheduling policy and run an exact schedulability test, including blocking time from shared resources and interrupt overhead.
  4. Use mutexes with priority inheritance or ceiling protocols. Never use plain binary semaphores for mutual exclusion between priorities.
  5. Keep ISRs short and defer work to tasks; protect every variable shared with an ISR.
  6. Schedule periodic loops against absolute time, and measure the jitter on target hardware under worst-case load.
  7. Add runtime monitoring: watchdogs, deadline-miss counters, and a defined safe behavior when a deadline is missed (see Safety in Cyber-Physical Systems).

The Series

Textbook Reading

Lee & Seshia, Introduction to Embedded Systems: A Cyber-Physical Systems Approach, 2nd ed.

The chapters in the book's Design part cover this page's material: 7 Sensors and Actuators, 8 Embedded Processors, 9 Memory Architectures, 10 Input and Output, 11 Multitasking, and 12 Scheduling. Chapter 16, Quantitative Analysis, covers execution-time analysis.

Book website and free PDF

Tutorials, Tools, and Interactive Learning

Wokwi

A browser-based simulator for ESP32, Raspberry Pi Pico, STM32, and Arduino boards with sensors, displays, and motors. Projects can be written in MicroPython, so you can experiment with timers, interrupts, and PWM without any hardware.

Open Wokwi

MicroPython

Python 3 for microcontrollers, with a machine module for pins, ADCs, PWM, timers, and interrupts. It includes an online board emulator.

Home Documentation Try it online

FreeRTOS

The most widely used open-source RTOS for microcontrollers (MIT license, 40+ architectures). Its beginner's guide explains tasks, priorities, queues, and mutexes with priority inheritance.

Home Beginner's guide

Zephyr RTOS

A Linux Foundation RTOS with broad board support, a device-tree hardware model, and built-in networking and security subsystems.

Documentation Getting started

Real-Time Linux (PREEMPT_RT)

The Linux Foundation's Real-Time Linux project, which coordinated the mainlining of PREEMPT_RT and documents real-time configuration.

Real-Time Linux wiki

SimSo

A Python scheduling simulator (built on SimPy) with over 25 uniprocessor and multiprocessor schedulers, including cache and overhead models. Use it to extend the simulators on this page.

SimSo project

Lingua Franca Embedded Systems Labs

A hands-on lab sequence on timing, concurrency, and modal models with the RP2040 robot platform, designed to accompany Lee & Seshia.

Embedded Systems Labs

Further Reading

Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment
C. L. Liu and J. W. Layland. Journal of the ACM, 20(1), 46–61, 1973. The founding paper of real-time scheduling theory: rate-monotonic optimality, the utilization bound, and EDF.
Priority Inheritance Protocols: An Approach to Real-Time Synchronization
L. Sha, R. Rajkumar, and J. P. Lehoczky. IEEE Transactions on Computers, 39(9), 1175–1185, 1990. Defines the priority inheritance and priority ceiling protocols.
Applying New Scheduling Theory to Static Priority Pre-emptive Scheduling
N. Audsley, A. Burns, M. Richardson, K. Tindell, and A. Wellings. Software Engineering Journal, 8(5), 284–292, 1993. The standard formulation of response-time analysis, including blocking and jitter.
Rate Monotonic vs. EDF: Judgment Day
G. C. Buttazzo. Real-Time Systems, 29(1), 5–26, 2005. A careful comparison that debunks common myths about both policies.
Real Time Scheduling Theory: A Historical Perspective
L. Sha et al. Real-Time Systems, 28(2–3), 101–155, 2004. Thirty years of scheduling theory and its adoption in practice, by the field's leading researchers.
The Worst-Case Execution-Time Problem: Overview of Methods and Survey of Tools
R. Wilhelm et al. ACM Transactions on Embedded Computing Systems, 7(3), 2008. The definitive survey of measurement-based and static WCET analysis.
A Survey of Hard Real-Time Scheduling for Multiprocessor Systems
R. I. Davis and A. Burns. ACM Computing Surveys, 43(4), Article 35, 2011. Partitioned, global, and hybrid multiprocessor scheduling.