A practical guide to rate-monotonic scheduling, priority inversion, and deadlock prevention in FreeRTOS.
Real-time operating systems (RTOS) are the backbone of mission-critical embedded systems. From automotive control units to medical infusion pumps, the scheduler’s ability to guarantee deadlines directly translates to system safety and reliability.
Task scheduling in an RTOS is fundamentally different from general-purpose OS scheduling. We are not optimizing for throughput or fairness — we are optimizing for determinism and deadline satisfaction.
RMS assigns priorities based on task periodicity: tasks with shorter periods receive higher priority. This is provably optimal among static-priority schedulers under certain conditions.
typedef struct {
uint32_t period_ms;
uint32_t execution_time_ms;
uint32_t deadline_ms;
uint8_t priority; // 0 = highest
uint32_t elapsed_ms;
} Task;
void rms_priority_assign(Task *tasks, size_t count) {
// Shorter period → higher priority
for (size_t i = 0; i < count; i++) {
tasks[i].priority =
(uint8_t)(count - 1 - (i * 255U / count));
}
}
For a set of n periodic tasks, the system is schedulable under RMS if the total utilization does not exceed the bound:
U = Σ(Ci / Ti) ≤ n(2^(1/n) - 1)
Where Ci is the worst-case execution time and Ti is the period. For large n, this bound approaches ln(2) ≈ 0.693.
Priority inversion occurs when a high-priority task is blocked by a low-priority task that holds a shared resource. This can lead to missed deadlines at the most critical moment.
// Unsafe — no protection against inversion
void read_sensor_data(uint8_t *buffer) {
acquire_lock(&sensor_lock); // Low-pri task holds this!
memcpy(buffer, sensor_buf, 64);
release_lock(&sensor_lock);
// ⚠️ Deadline may have already passed
}
When a low-priority task holds a lock needed by a higher-priority task, the kernel temporarily elevates the low-priority task to the high-priority level. This bounds the blocking time to a single critical section.
In safety-critical systems, deadlock is not an error — it’s a hazard. Our firmware must guarantee that lock acquisition follows a strict order.
Rules for embedded deadlock-free design:
#define RES_LCD 1
#define RES_SENSOR 2
#define RES_UART 3
// Always acquire in ascending order
void safe_update_display_and_log(uint8_t *data) {
acquire_lock_ordered(RES_LCD, 50); // ms timeout
acquire_lock_ordered(RES_UART, 50);
write_display(data);
write_log(data);
release_lock(RES_UART);
release_lock(RES_LCD);
}
When validating a real-time system, measure these metrics:
| Metric | Target | Critical |
|---|---|---|
| Jitter | < 1% of period | > 10% → unstable |
| Max latency | < 80% of deadline | > 95% → redesign |
| Context switch | < 10 µs | > 100 µs → check ISR |
A well-designed RTOS scheduler obeys the laws of real-time theory: utilization bounds, bounded blocking, and lock ordering. By applying RMS with priority inheritance and disciplined resource access, you can achieve deterministic behavior even under load.
For your next project, start by measuring your actual utilization against the RMS bound. If you exceed it, consider splitting tasks or migrating to a multi-rate design.