Choosing the Right Scheduling Policy in Linux
1. Introduction

In modern operating systems, it may seem like all processes are running simultaneously. However, in reality, we have a limited number of CPU cores that can execute instructions in parallel. As a result, most programs don’t run in true parallel—they execute concurrently, taking turns on the CPU.
To manage this time-sharing efficiently, the system relies on a central decision-maker—much like a conductor leading an orchestra. Just as the conductor decides which instrument plays and when, the scheduler decides which process gets access to the CPU and for how long.
The scheduler’s job is to divide the finite CPU time among all runnable processes. This not only maximizes system utilization but also creates the illusion that multiple programs are running at once. When there are more processes ready to run than available CPUs, some must wait. Deciding which process runs next is the core responsibility of the scheduler.
Just as an orchestra follows a musical score, the scheduler operates under a scheduling policy—a set of rules that determine how tasks are prioritized and selected. These policies shape the overall responsiveness and behavior of the system.
So what scheduling policies does the Linux kernel offer? And how can we choose the most suitable one based on our application's needs?
---
2. Linux Scheduling Policies
The Linux kernel offers two main categories of scheduling policies: the Completely Fair Scheduler (CFS) for general-purpose tasks, and real-time schedulers designed for time-critical processes.
2.1. Completely Fair Scheduler (CFS)
The CFS is the default way Linux decides which process should run on the CPU next—when we’re not dealing with real-time tasks.
The goal of CFS is to be as fair as possible: it tries to make sure every process gets a fair share of CPU time. Instead of giving each process a fixed time slot, it keeps track of how much time each one has already had, using something called virtual runtime. The process that has run the least recently gets picked next from the process ready list.
The CFS also allows users to influence scheduling using a nice value: a lower nice value means a process is more important and gets more time. Under the hood, this is turned into a weight—a number that helps the scheduler decide how much CPU time each process deserves.
Example: CFS and Nice Values
To understand how the CFS distributes CPU time based on process priority, we can experiment using this simple C program. It:
- Accepts a nice value as a command-line argument
- Pins the process to a specific CPU core (CPU 0)
- Runs a CPU-intensive loop
int main(int argc, char *argv[])
{
/* Check if the user provided the required argument */
if (argc < 2) {
printf("Usage: %s <nice_value>\n", argv[0]);
return 1;
}
/* Set nice value */
int nice_val = atoi(argv[1]);
/*Set process priority */
nice(nice_val);
/* Define and initialize CPU set */
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask);
/* Set CPU affinity to pin the process to CPU 0 */
if (sched_setaffinity(0, sizeof(mask), &mask) != 0)
{
perror("sched_setaffinity");
exit(EXIT_FAILURE);
}
/* Infinite loop to simulate a CPU-intensive task */
while (1)
{
}
return 0;
}
We run this code with three different “nice” values:
./fair_sched 0 # high priority
./fair_sched 10 # medium priority
./fair_sched 19 # low priority
With htop to monitor CPU usage, as shown in the screenshot below:

- The process with nice 0 dominated CPU usage.
- The one with nice 10 received significantly less.
- The nice 19 process barely ran.
2.2. Real-Time Schedulers
In contrast to the Completely Fair Scheduler, which tries to give all processes a fair share of CPU time, real-time schedulers in Linux are designed to meet strict timing requirements. They are used when certain tasks must run with minimal delay.
Linux offers two main real-time scheduling policies: SCHED_FIFO and SCHED_RR.
- SCHED_FIFO (First-In, First-Out): Runs processes in the order they become ready, and they can keep running as long as they want—unless a higher-priority real-time task arrives. There's no time slice, so they yield the CPU voluntarily or if they block.
- SCHED_RR (Round-Robin): Similar to FIFO but adds time slices. If two tasks have the same priority, they take turns using the CPU for a fixed time before the next one runs.
Real-time tasks have static priorities, meaning the kernel won’t recalculate or adjust them like it does for regular tasks. A higher-priority real-time task will always preempt a lower one.
Example: Real-Time Scheduling
We can demonstrate this by running two instances of this code with different priorities (50 and 10):
int main(int argc, char *argv[])
{
/* read priority */
if (argc != 2)
{
fprintf(stderr, "Usage: %s <priority>\n", argv[0]);
exit(EXIT_FAILURE);
}
int priority = atoi(argv[1]);
if (priority < 1 || priority > 99)
{
printf(stderr, "Priority must be between 1 and 99\n");
exit(EXIT_FAILURE);
}
struct sched_param param;
int policy = SCHED_FIFO;
/* Pin the process to core 0 */
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == -1)
{
perror("sched_setaffinity");
exit(EXIT_FAILURE);
}
/* Set real-time priority */
param.sched_priority = priority;
/* apply real-time scheduling */
if (sched_setscheduler(0, policy, ¶m) == -1)
{
perror("sched_setscheduler");
exit(EXIT_FAILURE);
}
printf("Running under SCHED_FIFO with priority %d\n", param.sched_priority);
/**CPU-intensive task */
while (1)
{
}
return 0;
}
With htop, we can observe that the process with priority 10 never gets scheduled until the process with priority 50 terminates its execution:

---
3. How to Choose the Scheduling Policy for a Process
Before choosing a scheduling policy, it’s important to understand the nature of the task your process performs. Broadly speaking, Linux processes can be classified as either CPU-bound or I/O-bound.
3.1. Process Bounds
I/O-Bound:
These processes spend most of their time waiting for I/O operations (disk, network, user input, etc.). Their CPU usage is relatively low, and they often sleep while waiting for data.
For example, most graphical user interface (GUI) applications are I/O-bound, even if they never read from or write to the disk, because they spend most of their time waiting on user interaction via the keyboard and mouse.

CPU-Bound:
CPU-bound processes spend most of their time actively executing code, with very little waiting on I/O operations. Since they rarely block, these processes typically run continuously until they are explicitly preempted. Because they aren't driven by external input or user interactions, system responsiveness doesn't require the scheduler to switch to them frequently.
Scheduling policies for CPU-bound tasks often allow them to run less often but for longer uninterrupted periods when they do get CPU time. A classic example of a CPU-bound process is one stuck in an infinite loop. More practical examples include applications that perform heavy mathematical computations, such as digital signal processing or data analysis.

To easily distinguish between the two types of processes, we can ask: Why does the CPU stop running a given task?
- If the process voluntarily yields, it's typically I/O-bound.
- If the process is preempted by the scheduler because it uses the CPU for extended periods without waiting, it’s considered CPU-bound.
3.2. Scheduling Policy Based on Process Type
Choosing the right scheduling policy depends heavily on the nature of the application and whether it is CPU-bound or I/O-bound.
- Interactive or user-facing applications—such as graphical user interfaces—benefit from low latency and fast responsiveness. In such cases, prioritizing I/O-bound processes is often a good idea.
- Processes that must respond within strict and deterministic timing constraints are better suited for real-time scheduling policies like SCHED_FIFO or SCHED_RR.
- For background computational tasks that don't require immediate responsiveness, adjusting the nice value under CFS can help minimize their impact on more critical processes.
In general, selecting the appropriate scheduling policy should consider how sensitive the task is to latency, how frequently it blocks on I/O, and how predictable its execution pattern is.
4.Conclusion
Understanding Linux scheduling policies empowers developers to align process behavior with system goals, ensuring better responsiveness and performance. By choosing the right policy based on task characteristics, you can make more efficient use of CPU time and improve overall system efficiency.