SCHED_RR and SCHED_FIFO Policies

 

SCHED_RR and SCHED_FIFO Policies
Chapter 35.2.1–35.2.2 — Deep dive into both realtime scheduling policies, preemption rules, and queue behavior
← Chapter 35 Index  /  SCHED_RR and SCHED_FIFO

SCHED_RR: Round-Robin Realtime Policy

SCHED_RR (Round-Robin) is one of the two POSIX realtime scheduling policies. It works like the familiar round-robin of SCHED_OTHER, but only among processes at the same realtime priority level, and with strict priority preemption between levels.

When a SCHED_RR process is scheduled, it receives a fixed-length time slice. It runs until one of the following happens:

# Event What happens to the process?
1 Time slice expires Placed at the back of the queue for its priority level. Next process at same priority runs.
2 Voluntarily relinquishes CPU (blocking call or sched_yield()) Placed at the back of the queue for its priority level.
3 Process terminates Removed from queue entirely.
4 Preempted by higher-priority process Remains at the head of its priority queue. Resumes with the remainder of its time slice when the higher-priority process finishes.

SCHED_RR: Time Slice Round-Robin at Same Priority

P1 (Prio 50)
P2 (Prio 50)
P3 (Prio 50)
P1 again
P2 again
T=0 slice
T=1 slice
T=2 slice
T=3 slice
T=4 slice
Three SCHED_RR processes at the same priority level, rotating through equal time slices.
⚡ Preemption and the head-of-queue rule: When a SCHED_RR process is preempted by a higher-priority process, it stays at the head of its priority queue (not moved to the back). When the higher-priority process finishes, the preempted process continues with the remainder of its original time slice — as if it was never interrupted.

SCHED_FIFO: First-In First-Out Realtime Policy

SCHED_FIFO is similar to SCHED_RR with one critical difference: there is no time slice. A SCHED_FIFO process, once it gets the CPU, will hold it indefinitely until one of three things happens:

# Event What happens to the process?
1 Voluntarily relinquishes CPU (blocking call or sched_yield()) Placed at the back of the queue for its priority level.
2 Process terminates Removed from queue entirely.
3 Preempted by higher-priority process Remains at the head of its priority queue. Resumes immediately when the higher-priority process finishes (blocks or terminates).

SCHED_FIFO: No Time Slice — Holds CPU Until Done or Blocked

P1 (FIFO, Prio 50) — runs until blocks
P2 Prio 80 preempts!
P1 resumes
P3 (after P1 yields)
P1 holds the CPU for its full duration. Only a higher-priority process (P2 at 80) can interrupt. After P2 finishes, P1 resumes. P3 only gets CPU after P1 voluntarily yields.
⚠️ Danger of SCHED_FIFO: A SCHED_FIFO process that never blocks and never calls sched_yield() will completely starve all lower-priority processes (including SCHED_OTHER processes). If it goes into an infinite loop (runaway process), it can lock up your entire system. Always have safeguards when using SCHED_FIFO.

SCHED_RR vs SCHED_FIFO vs SCHED_OTHER: Side-by-Side

Feature SCHED_OTHER SCHED_RR SCHED_FIFO
Has time slice ✓ Yes ✓ Yes ✗ No
Priority range Nice -20 to +19 RT 1–99 RT 1–99
Preempts SCHED_OTHER N/A ✓ Always ✓ Always
Can starve other processes ✗ No Only lower-priority ✓ Yes (no time limit)
Requires privileges ✗ No ✓ Root/CAP_SYS_NICE ✓ Root/CAP_SYS_NICE
POSIX standard ✓ Yes ✓ Yes ✓ Yes
Use case Normal processes Shared RT workloads Critical RT tasks

Common Preemption Triggers (Both SCHED_RR and SCHED_FIFO)

Both realtime policies share the same set of conditions that cause the currently running process to be preempted by another process:

Trigger 1
A higher-priority process that was blocked becomes unblocked — for example, an I/O operation it was waiting for completed. This immediately preempts the running process.
Trigger 2
The priority of another process is raised (via sched_setscheduler() or sched_setparam()) to a level higher than the current process.
Trigger 3
The priority of the running process is lowered to below that of some other runnable process.

💻 Code Example 1: Set Process to SCHED_FIFO

This program elevates itself to SCHED_FIFO realtime scheduling. Must be run as root.


/* set_fifo.c
 * Switches the calling process to SCHED_FIFO with a given priority.
 * WARNING: A runaway SCHED_FIFO process can lock up your system.
 *          Always test in a VM or with a watchdog.
 *
 * Compile: gcc set_fifo.c -o set_fifo
 * Run:     sudo ./set_fifo 25
 */
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    struct sched_param sp;
    int policy;
    int desired_prio;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <priority>\n", argv[0]);
        fprintf(stderr, "  priority: %d to %d\n",
                sched_get_priority_min(SCHED_FIFO),
                sched_get_priority_max(SCHED_FIFO));
        exit(EXIT_FAILURE);
    }

    desired_prio = atoi(argv[1]);

    /* Validate priority is within allowed range */
    int min_p = sched_get_priority_min(SCHED_FIFO);
    int max_p = sched_get_priority_max(SCHED_FIFO);

    if (desired_prio < min_p || desired_prio > max_p) {
        fprintf(stderr, "Priority %d out of range [%d, %d]\n",
                desired_prio, min_p, max_p);
        exit(EXIT_FAILURE);
    }

    /* Show current scheduling info */
    policy = sched_getscheduler(0);
    printf("Before: policy=%s, PID=%d\n",
           (policy == SCHED_OTHER) ? "SCHED_OTHER" :
           (policy == SCHED_RR)    ? "SCHED_RR"    :
           (policy == SCHED_FIFO)  ? "SCHED_FIFO"  : "OTHER",
           (int)getpid());

    /* Switch to SCHED_FIFO */
    sp.sched_priority = desired_prio;
    if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
        perror("sched_setscheduler (need root/CAP_SYS_NICE)");
        exit(EXIT_FAILURE);
    }

    /* Verify the change */
    policy = sched_getscheduler(0);
    if (sched_getparam(0, &sp) == -1) {
        perror("sched_getparam");
        exit(EXIT_FAILURE);
    }

    printf("After:  policy=%s, priority=%d, PID=%d\n",
           (policy == SCHED_FIFO) ? "SCHED_FIFO" : "OTHER",
           sp.sched_priority,
           (int)getpid());

    /*
     * === CRITICAL SAFETY ===
     * At this point, this process runs as SCHED_FIFO.
     * Do your time-critical work here.
     * Always ensure you call a blocking syscall or sched_yield()
     * to give other processes a chance to run!
     */
    printf("Now running as SCHED_FIFO priority %d\n", sp.sched_priority);
    printf("Calling sched_yield() to be a good citizen...\n");
    sched_yield();   /* Move to back of FIFO queue at this priority */

    /* Switch back to normal scheduling before exit */
    sp.sched_priority = 0;
    if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) {
        perror("sched_setscheduler (reset to SCHED_OTHER)");
    }
    printf("Restored to SCHED_OTHER\n");

    return EXIT_SUCCESS;
}
    

💻 Code Example 2: SCHED_RR with Priority Comparison

This program forks a child, sets both parent and child to SCHED_RR at the same priority, and shows how they take turns.


/* rr_demo.c
 * Parent and child both run as SCHED_RR at same priority.
 * They take turns consuming CPU in round-robin fashion.
 *
 * Compile: gcc rr_demo.c -o rr_demo
 * Run:     sudo ./rr_demo
 */
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>

/* Busy-wait for approximately 'ms' milliseconds.
 * Uses clock_gettime to measure actual elapsed time. */
void busy_wait_ms(long ms)
{
    struct timespec start, now;
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (;;) {
        clock_gettime(CLOCK_MONOTONIC, &now);
        long elapsed_ms = (now.tv_sec - start.tv_sec) * 1000
                        + (now.tv_nsec - start.tv_nsec) / 1000000;
        if (elapsed_ms >= ms)
            break;
        /* Tight busy loop — intentionally no sleep */
    }
}

void run_as_rr(int priority)
{
    struct sched_param sp;
    sp.sched_priority = priority;
    if (sched_setscheduler(0, SCHED_RR, &sp) == -1) {
        perror("sched_setscheduler");
        exit(EXIT_FAILURE);
    }
}

int main(void)
{
    int rr_priority;
    pid_t child;

    /* Use a low realtime priority for safety */
    rr_priority = sched_get_priority_min(SCHED_RR) + 5;
    printf("Using SCHED_RR priority: %d\n\n", rr_priority);

    child = fork();
    if (child == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (child == 0) {
        /* ---- CHILD PROCESS ---- */
        run_as_rr(rr_priority);
        for (int i = 0; i < 3; i++) {
            printf("[Child  PID=%d] Running burst %d\n", (int)getpid(), i+1);
            busy_wait_ms(100);  /* Use CPU for 100ms */
            sched_yield();      /* Give up CPU (go to back of queue) */
        }
        printf("[Child  PID=%d] Done\n", (int)getpid());
        exit(EXIT_SUCCESS);
    } else {
        /* ---- PARENT PROCESS ---- */
        run_as_rr(rr_priority);
        for (int i = 0; i < 3; i++) {
            printf("[Parent PID=%d] Running burst %d\n", (int)getpid(), i+1);
            busy_wait_ms(100);  /* Use CPU for 100ms */
            sched_yield();      /* Give up CPU (go to back of queue) */
        }
        printf("[Parent PID=%d] Done\n", (int)getpid());

        /* Restore normal scheduling before wait() */
        struct sched_param sp0 = { .sched_priority = 0 };
        sched_setscheduler(0, SCHED_OTHER, &sp0);

        wait(NULL);
    }

    return EXIT_SUCCESS;
}
    
Expected output (interleaved, order may vary):
[Parent PID=…] Running burst 1
[Child PID=…] Running burst 1
[Parent PID=…] Running burst 2
[Child PID=…] Running burst 2

🎯 Interview Questions

Q1. What is the key difference between SCHED_RR and SCHED_FIFO?
SCHED_RR has a time slice. When the time slice expires, the process is moved to the back of the queue for its priority level, and the next process at that level gets the CPU. This allows multiple SCHED_RR processes at the same priority to share the CPU fairly.

SCHED_FIFO has no time slice. A SCHED_FIFO process holds the CPU until it voluntarily blocks/yields or is preempted by a higher-priority process. It will never be forced off the CPU just because time has passed.

Q2. When a SCHED_RR process is preempted by a higher-priority process, does it go to the front or back of its queue?
It goes to the front (head) of its priority queue — not the back. This is because preemption is not the process’s “fault.” When the higher-priority process finishes, the preempted SCHED_RR process resumes with the remainder of its time slice. (Compare: if a SCHED_RR process’s time slice naturally expires, it goes to the back of the queue.)
Q3. Two processes — one SCHED_RR and one SCHED_FIFO — both have priority 50. Which runs first?
Since they have the same priority, they share the same priority queue. Which one runs first depends on their order in the queue. The one that was added to the queue first (or was last scheduled) will run first. Neither policy gives priority to the other when both are at the same priority level.
Q4. What are the three conditions that cause a realtime process to be preempted?
1. A higher-priority process that was blocked becomes unblocked (e.g., I/O completes).
2. The priority of another process is raised above the running process’s priority.
3. The priority of the running process is lowered below some other runnable process.
Q5. Why is SCHED_FIFO dangerous compared to SCHED_RR for most applications?
Because SCHED_FIFO has no time slice. A SCHED_FIFO process that goes into an infinite loop (or simply has a very long computation) will hold the CPU indefinitely, completely starving all lower-priority processes — including all SCHED_OTHER processes and the shell. This can lock up the entire system. SCHED_RR, by contrast, guarantees that other processes at the same priority level will eventually get the CPU via the time slice mechanism.
Q6. In what way is SCHED_RR different from SCHED_OTHER (standard round-robin)?
Two main differences: (1) SCHED_RR has strict priority levels — a higher-priority SCHED_RR process always completely excludes lower-priority processes from the CPU, while SCHED_OTHER’s nice value only “weights” the scheduler. (2) SCHED_RR allows you to precisely control the scheduling order, while SCHED_OTHER’s nice value is just a hint and a low-priority SCHED_OTHER process always gets some CPU.

Leave a Reply

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