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
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
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:
sched_setscheduler() or sched_setparam()) to a level higher than the current 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;
}
[Parent PID=…] Running burst 1
[Child PID=…] Running burst 1
[Parent PID=…] Running burst 2
[Child PID=…] Running burst 2
…
🎯 Interview Questions
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.
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.
