Realtime Process Scheduling: Overview

 

Realtime Process Scheduling: Overview
Chapter 35.2 — Why standard scheduling fails for real-time applications, and what POSIX provides
← Chapter 35 Index  /  Realtime Scheduling Overview

Why Standard Scheduling Is Not Enough

The round-robin time-sharing scheduler (SCHED_OTHER) works great for desktop and server workloads. But some applications have much stricter requirements. Consider a vehicle navigation system, an industrial robot controller, or a cardiac monitor. These systems must respond to events within a guaranteed maximum time. If the scheduler decides to run a background process for 50ms when the navigation system urgently needs the CPU, the result could be catastrophic.

Such applications are called real-time applications, and they have two key requirements that the standard scheduler cannot satisfy:

⏱️ Guaranteed Response Time

A high-priority process must be able to seize the CPU from any currently running process within a known, short time. Standard scheduling offers no such guarantee.

🔒 Exclusive CPU Access

A high-priority realtime process must be able to hold the CPU exclusively until it finishes its critical task, without being preempted by lower-priority processes.

Soft Realtime vs Hard Realtime

The POSIX realtime API provides what is called soft realtime. This is an important distinction to understand.

Feature Soft Realtime (POSIX API) Hard Realtime (RTOS)
Guaranteed max response time ✗ No ✓ Yes
Priority-based preemption ✓ Yes ✓ Yes
Exclusion of lower-priority processes ✓ Yes ✓ Yes
Works on standard Linux kernel ✓ Yes ✗ Needs PREEMPT_RT patch
Overhead on normal processes ✓ Low ✗ Higher
📌 Key Point: The standard Linux kernel (without PREEMPT_RT patches) provides soft realtime only. It gives you better control over which process runs, but cannot mathematically guarantee exactly when it will run. For true hard realtime (like avionics or medical devices), you need a dedicated RTOS or a patched Linux kernel.

The POSIX Realtime Scheduling Policies

The POSIX standard defines a realtime scheduling API (originally from POSIX.1b) that adds two realtime policies to the existing standard policy:

SCHED_OTHER (Standard Round-Robin)

Default policy • Nice values -20 to +19 • All normal user processes
↑ Always preempted by realtime processes ↑
🔄 SCHED_RR

Round-Robin Realtime
• Has a time slice
• Processes at same priority share CPU
• Priority 1 (low) to 99 (high)
➡️ SCHED_FIFO

First-In First-Out Realtime
• No time slice
• Runs until blocks or yields
• Priority 1 (low) to 99 (high)
Core Rule: Any process running under SCHED_RR or SCHED_FIFO always preempts any process running under SCHED_OTHER, regardless of nice values. A SCHED_OTHER process at nice -20 will still be pushed off the CPU if a SCHED_FIFO process at priority 1 becomes runnable.

Realtime Priority Levels: 1 to 99

Linux provides 99 realtime priority levels, numbered from 1 (lowest) to 99 (highest). These levels are completely separate from the nice value system.

Priority 1 (Lowest RT)Priority 99 (Highest RT)

The SCHED_RR and SCHED_FIFO policies share the same priority range (1–99). A SCHED_RR process at priority 50 and a SCHED_FIFO process at priority 50 are considered equal in terms of priority — which one runs first depends on their position in the queue for that priority level.

🔧 Portability Tip: Different UNIX systems have different priority ranges (Solaris: 0–59, FreeBSD: 0–31). Never hard-code priority values in your application. Always use sched_get_priority_min(policy) and sched_get_priority_max(policy) to get the valid range at runtime.

How the Queue Model Works

Each priority level maintains its own queue of runnable processes. The scheduler always picks from the front of the highest non-empty queue.

Priority 99
P-A (FIFO)
→ next to run
← CPU goes here first
Priority 50
P-B (RR)
P-C (FIFO)
Waits while P99 runs
Priority 1
P-D (RR)
Runs last
SCHED_OTHER
P-E
P-F
P-G
Only runs when no RT process is runnable

⚠️ Multiprocessor Systems: A Critical Nuance

On a multiprocessor or hyperthreaded system, each CPU maintains its own separate run queue. Processes are prioritized per CPU queue, not system-wide. This can lead to surprising behavior:

CPU 0
Running: Process B (Priority 30)
Waiting: Process A (Priority 20) ← blocked!
CPU 1
Running: Process C (Priority 10)
⚠️ On CPU 0, Process A (priority 20) cannot preempt Process B (priority 30) — that is correct. But notice: CPU 1 is running Process C at priority 10 even though Process A (priority 20) is waiting! This happens because each CPU has its own queue. Process A is stuck behind Process B on CPU 0.

To avoid this in time-critical applications, use the CPU affinity API (covered in File 7) to isolate critical realtime processes to dedicated CPUs.

💻 Code Example 1: Query Priority Ranges

Use sched_get_priority_min() and sched_get_priority_max() to discover the valid realtime priority range on the current system — never hard-code these values.


/* query_rt_range.c
 * Shows the valid realtime priority ranges for SCHED_FIFO and SCHED_RR.
 * Compile: gcc query_rt_range.c -o query_rt_range
 * Run:     ./query_rt_range
 */
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>

void show_range(int policy, const char *name)
{
    int min_prio, max_prio;

    min_prio = sched_get_priority_min(policy);
    if (min_prio == -1) {
        perror("sched_get_priority_min");
        return;
    }

    max_prio = sched_get_priority_max(policy);
    if (max_prio == -1) {
        perror("sched_get_priority_max");
        return;
    }

    printf("Policy %-12s : min = %3d, max = %3d\n",
           name, min_prio, max_prio);
}

int main(void)
{
    printf("=== Realtime Scheduling Priority Ranges ===\n\n");

    show_range(SCHED_FIFO,  "SCHED_FIFO");
    show_range(SCHED_RR,    "SCHED_RR");
    show_range(SCHED_OTHER, "SCHED_OTHER");

    /* Portable way to specify a mid-range realtime priority */
    int min_rt = sched_get_priority_min(SCHED_FIFO);
    int max_rt = sched_get_priority_max(SCHED_FIFO);
    int mid_rt = min_rt + (max_rt - min_rt) / 2;

    printf("\nPortable mid-range RT priority: %d\n", mid_rt);
    printf("One step above minimum:         %d\n", min_rt + 1);
    printf("One step below maximum:         %d\n", max_rt - 1);

    return EXIT_SUCCESS;
}
    
Expected output on Linux:
Policy SCHED_FIFO : min = 1, max = 99
Policy SCHED_RR : min = 1, max = 99
Policy SCHED_OTHER : min = 0, max = 0
Portable mid-range RT priority: 50

💻 Code Example 2: Check Current Process’s Scheduling Policy

Read your own process’s scheduling policy and priority, and detect whether you are running under a realtime policy.


/* check_my_policy.c
 * Reads and prints the scheduling policy and priority of the calling process.
 * Compile: gcc check_my_policy.c -o check_my_policy
 * Run:     ./check_my_policy
 *          sudo ./check_my_policy  (if changed by parent or env)
 */
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>

const char *policy_name(int policy)
{
    switch (policy) {
    case SCHED_OTHER: return "SCHED_OTHER (standard round-robin)";
    case SCHED_FIFO:  return "SCHED_FIFO  (realtime FIFO)";
    case SCHED_RR:    return "SCHED_RR    (realtime round-robin)";
#ifdef SCHED_BATCH
    case SCHED_BATCH: return "SCHED_BATCH (Linux batch)";
#endif
#ifdef SCHED_IDLE
    case SCHED_IDLE:  return "SCHED_IDLE  (Linux idle)";
#endif
    default:          return "UNKNOWN";
    }
}

int main(void)
{
    int policy;
    struct sched_param sp;

    /* Pass 0 to operate on the calling process */
    policy = sched_getscheduler(0);
    if (policy == -1) {
        perror("sched_getscheduler");
        exit(EXIT_FAILURE);
    }

    if (sched_getparam(0, &sp) == -1) {
        perror("sched_getparam");
        exit(EXIT_FAILURE);
    }

    printf("=== My Scheduling Information ===\n");
    printf("Policy   : %s\n", policy_name(policy));
    printf("Priority : %d\n", sp.sched_priority);

    /* Check if we are under a realtime policy */
    if (policy == SCHED_FIFO || policy == SCHED_RR) {
        printf("Status   : REALTIME process\n");
        printf("Priority range: %d to %d\n",
               sched_get_priority_min(policy),
               sched_get_priority_max(policy));
    } else {
        printf("Status   : Normal (non-realtime) process\n");
        printf("Note     : Realtime priority has no meaning for this policy\n");
    }

    return EXIT_SUCCESS;
}
    
Normal run output:
Policy : SCHED_OTHER (standard round-robin)
Priority : 0
Status : Normal (non-realtime) process

🎯 Interview Questions

Q1. What is the difference between soft realtime and hard realtime?
Hard realtime guarantees a mathematically provable maximum response time — a deadline is never missed. Used in avionics, pacemakers, nuclear control. Requires a dedicated RTOS or a specially patched Linux kernel (PREEMPT_RT).

Soft realtime provides best-effort priority-based preemption. A high-priority process will usually get the CPU quickly, but there is no absolute guarantee. The POSIX realtime API on standard Linux provides soft realtime.

Q2. If a SCHED_OTHER process has nice value -20 and a SCHED_FIFO process has realtime priority 1, which runs first?
The SCHED_FIFO process wins. Any runnable realtime process (SCHED_FIFO or SCHED_RR, any priority from 1 to 99) always preempts any SCHED_OTHER process, regardless of the nice value. Nice values only differentiate within SCHED_OTHER; they have no effect on realtime policies.
Q3. How many realtime priority levels does Linux provide, and what is the range?
Linux provides 99 realtime priority levels, numbered 1 (lowest) to 99 (highest). This range applies equally to both SCHED_RR and SCHED_FIFO. POSIX only requires at least 32 discrete levels; Linux goes beyond this.
Q4. Why should you never hard-code realtime priority values like 50 or 99 in your program?
Because the priority range is implementation-defined and varies across UNIX systems. Linux uses 1–99, Solaris 8 uses 0–59, FreeBSD 6.1 uses 0–31. A hard-coded value of 99 on a system where the max is 31 would cause an error. Always use sched_get_priority_min() and sched_get_priority_max() and express priorities relative to these values.
Q5. On a dual-CPU system, is it guaranteed that a high-priority realtime process always gets the CPU immediately?
No. On multiprocessor systems, each CPU has a separate run queue. A process is only prioritized within its own CPU’s queue. A high-priority process waiting in CPU 0’s queue will not automatically migrate to CPU 1 even if CPU 1 is running a lower-priority process. To guarantee placement, use the CPU affinity API to pin processes to specific CPUs.

Leave a Reply

Your email address will not be published. Required fields are marked *