A Linux system may run a web server, a background backup, an audio engine, and a control loop at the same time. These workloads do not merely need different numeric priorities. They need different rules for deciding who should run.
Linux handles this by organizing scheduling behavior into scheduling classes. Each class implements a scheduling model for a family of workloads. The kernel first considers which class has eligible runnable work, then lets that class choose one of its threads.
This distinction explains why increasing the nice value of a normal process cannot make it outrank a real-time thread, why SCHED_FIFO behaves differently from SCHED_RR, and why the user-visible SCHED_IDLE policy is not the same thing as the kernel's idle thread.
Linux scheduling terminology is easy to mix up because three related ideas appear together.
A scheduling policy is the user-visible rule assigned to a thread. Examples include SCHED_OTHER, SCHED_FIFO, and SCHED_DEADLINE.
A policy has scheduling parameters. The relevant parameter depends on the policy:
A scheduling class is the kernel implementation that manages one or more policies. It defines how its runnable threads are ordered and how the next thread is selected.
For example, SCHED_FIFO and SCHED_RR are two policies handled by the real-time scheduling class. They share the same fixed-priority ordering across priority levels, but they use different rules among threads at the same priority.
The kernel schedules threads, not whole processes. Different threads in one multithreaded process can therefore have different policies and parameters.
It is tempting to imagine that every runnable thread receives one number and the highest number always wins. Linux does not work that way.
The kernel's main scheduling classes have a precedence order. Conceptually, a current Linux system looks like this:
| Precedence | Class | Policies it serves |
|---|---|---|
| Highest | stop | Internal kernel work |
| deadline | SCHED_DEADLINE | |
| real-time | SCHED_FIFO, SCHED_RR | |
| fair | SCHED_OTHER, SCHED_BATCH, SCHED_IDLE | |
| ext | Optional BPF-defined scheduling | |
| Lowest | idle | The per-CPU idle thread |
A class only gets asked for a thread when every class above it has none to offer.
The scheduler looks for the highest-precedence class that currently has eligible runnable work. That class then applies its own rules to choose a thread.
Eligibility matters. A deadline thread that has consumed its reserved runtime may be throttled until its budget is replenished. A real-time group may also be restricted by configured bandwidth controls. In such cases, a lower class can run even though a higher-class thread has not finished all of its work.
The ext class in the diagram is optional and configurable. It actively controls work only when the kernel supports extensible scheduling and a BPF scheduler is loaded. Its interaction with the fair class also depends on whether that scheduler takes control of selected threads or performs a full switch. It should not be assumed to be available on every Linux machine.
Two consequences of class precedence are especially important:
SCHED_OTHER thread with nice -20 does not outrank an eligible runnable SCHED_FIFO thread.The principal policies can be summarized as follows:
| Policy | Usual kernel class | Main parameters | Intended use |
|---|---|---|---|
SCHED_OTHER / SCHED_NORMAL | Fair | Nice value | Ordinary interactive and server work |
SCHED_BATCH | Fair | Nice value | CPU-intensive, noninteractive work |
SCHED_IDLE | Fair | No effective nice value | Work that should run only with very weak priority |
SCHED_FIFO | Real-time | Static priority | Fixed-priority real-time work without equal-priority time slicing |
SCHED_RR | Real-time | Static priority and quantum | Fixed-priority real-time work with equal-priority rotation |
SCHED_DEADLINE | Deadline | Runtime, deadline, and period | Reserved CPU bandwidth with time constraints |
SCHED_EXT | Extensible | BPF scheduler-defined behavior | Experimental or specialized scheduling policies |
SCHED_OTHER is the POSIX API name for the normal Linux policy. Kernel code and tools may call the same policy SCHED_NORMAL.
The fair policies require a static scheduling priority of zero. Their internal ordering is not controlled through the real-time priority field.
Linux-specific policies and newer features depend on kernel configuration, kernel version, C library headers, and user-space tools. A policy appearing in documentation does not prove that a particular machine supports it.
Most application threads use the fair scheduling class. It handles normal server processes, shells, compilers, databases, and almost every program that has not explicitly requested another policy.
The word fair does not mean every thread receives exactly the same amount of CPU time. It means the scheduler attempts to divide CPU service according to weights and policy-specific rules while preserving responsiveness.
Linux began transitioning its fair class to EEVDF-based selection in version 6.6, and current mainline kernels use that design. At a high level, the scheduler tracks whether a thread has received more or less service than its weighted share and favors eligible work with an earlier virtual deadline. The important point here is that fair-class threads compete using fair-class accounting rather than real-time priorities.
SCHED_OTHERSCHED_OTHER is the default policy. Its static scheduling priority is zero, and its relative CPU weight is influenced by the thread's nice value.
Linux nice values range from:
A more favorable nice value gives a CPU-bound thread a larger share when it contends with other runnable fair-class threads. It does not reserve a specific percentage, guarantee a response time, or move the thread into a higher scheduling class.
Suppose two CPU-bound SCHED_OTHER threads are pinned to one CPU:
Thread A receives a larger weight and will usually accumulate CPU time faster. If B is the only runnable thread, however, B can use the whole CPU. Nice affects competition; it is not a utilization cap.
On Linux, niceness is a per-thread attribute. Threads in the same process can have different nice values, even though many user-space tools present a process-oriented view.
SCHED_BATCHSCHED_BATCH is designed for CPU-intensive work that does not need interactive wakeup behavior. It still belongs to the fair class, still uses nice values, and still competes according to fair scheduling.
The scheduler treats a batch thread as noninteractive and applies a small penalty when it wakes. This can favor throughput and reduce disruption to interactive work.
Suitable examples include compression, offline report generation, and a build job running in the background.
SCHED_BATCH does not mean “run only when the CPU would otherwise be idle.” A batch thread can receive substantial CPU time while normal threads are runnable, according to their relative weights.
SCHED_IDLESCHED_IDLE is an extremely weak fair-class policy. Its priority is lower than that of a SCHED_OTHER thread at nice +19, and changing its nice value does not make it stronger.
It is useful for work that should make progress only when more important fair work does not need the CPU, such as opportunistic maintenance.
If a SCHED_IDLE thread is the only runnable work, it can run. The policy does not limit it to a fixed percentage of the processor.
Despite its name, SCHED_IDLE is not the kernel's internal idle scheduling class. It is a user-selectable policy for real work, implemented within the fair class. The actual per-CPU idle thread belongs to a separate internal class and runs only when no other class has eligible work.
The real-time class handles SCHED_FIFO and SCHED_RR. Both use a static real-time priority.
On Linux, real-time priorities normally range from:
Portable programs should ask the operating system for the supported minimum and maximum instead of hard-coding the Linux range.
The class first chooses the highest priority level containing runnable work. A runnable thread at priority 70 takes precedence over every real-time thread at priority 69 or below, as well as ordinary fair-class threads.
The policy matters when deciding how runnable threads at the same priority share the CPU.
SCHED_FIFOSCHED_FIFO uses first-in, first-out ordering among runnable threads at the same real-time priority. It has no time quantum.
Once a SCHED_FIFO thread starts running, it continues until one of these events occurs:
If it is preempted by a higher-priority thread, it remains at the front of its own priority queue and resumes when the higher-priority work stops being runnable.
Consider three runnable threads on one CPU:
If F1 is at the front of priority 60, it can continue indefinitely. F2 does not receive a periodic turn merely because it has the same priority, and F3 cannot run while any priority-60 thread remains eligible and runnable.
This is why an accidental busy loop under a high SCHED_FIFO priority can make a system difficult to control.
SCHED_RRSCHED_RR uses the same real-time priority ordering as SCHED_FIFO, but introduces a time quantum among equal-priority round-robin threads.
If R1 and R2 are both runnable at priority 60:
When R1 consumes its quantum, it moves behind the other runnable threads at that priority. A higher-priority thread still preempts it immediately. When R1 later resumes after such a preemption, it receives the unused part of its previous quantum rather than automatically starting a new one.
Round Robin therefore prevents one continuously runnable SCHED_RR thread from monopolizing its priority level. It does not make lower real-time priorities or fair-class threads equal participants.
Neither real-time policy creates a deadline guarantee by itself. A high-priority thread can still finish late because its own execution is too long, it blocks on a resource, interrupts delay it, or higher-class work interferes.
SCHED_DEADLINE gives a thread a CPU reservation described by three values:
They must satisfy:
For example:
This describes a thread that may require up to two milliseconds of CPU service, with that service due within eight milliseconds, repeated no more frequently than every ten milliseconds.
Linux combines earliest-deadline-first selection with a constant-bandwidth mechanism. Among eligible deadline threads, earlier absolute deadlines receive precedence. If a thread consumes its runtime budget, the kernel throttles it until the reservation is replenished. This prevents one admitted deadline thread from taking more CPU bandwidth than it declared.
Before accepting a deadline reservation, the kernel performs admission control. A request can fail when the available CPU capacity cannot safely accommodate it. Admission is essential because accepting more reserved work than the machine can supply would make the reservations meaningless.
The deadline class takes precedence over the fixed-priority real-time class. However, a deadline thread whose budget is exhausted is not eligible to keep running merely because its code has more work to do.
SCHED_DEADLINE is configured through Linux's extended scheduling interface, sched_setattr(), rather than the traditional sched_setscheduler() interface. Creating deadline reservations normally requires elevated scheduling privileges.
SCHED_EXTLinux also supports an optional extensible scheduling class called sched_ext. It allows a scheduler implemented with BPF to control task placement and selection without replacing the kernel or loading a conventional kernel module.
This is an advanced facility rather than a universally available policy:
SCHED_EXT.Linux 6.12 added SCHED_EXT support. When no BPF scheduler is loaded, a thread marked for SCHED_EXT is handled like normal fair-scheduled work. If a loaded BPF scheduler fails, stalls, or is unloaded, the kernel falls back to fair scheduling instead of leaving controlled threads permanently stranded.
A sched_ext scheduler can operate in two broad modes. In a partial switch it manages explicitly selected SCHED_EXT threads, while ordinary fair-class work keeps higher class precedence. In a full switch it can take control of threads that would normally use SCHED_OTHER, SCHED_BATCH, or SCHED_IDLE.
The feature is useful for experimentation and specialized data-center workloads, but software should detect its availability rather than assuming it exists.
Two scheduling classes are primarily kernel implementation details.
The stop class has the highest precedence. It supports rare kernel operations that must stop ordinary execution on a CPU, such as parts of CPU migration and machine-wide synchronization. Applications cannot select this class as a scheduling policy.
The idle class has the lowest precedence and owns each CPU's idle thread. The kernel selects that thread only when no other class has eligible runnable work. Running the idle thread allows the processor to wait efficiently and possibly enter a power-saving state.
These classes complete the precedence chain, but they are not knobs that ordinary programs set.
Assume the following threads are allowed to run on one CPU:
At first, all six are runnable and D still has deadline budget:
Deadline task D is chosen first. Only after it finishes do FIFO task F and then RR task R run, followed by the fair class selecting among N, B, and I.
D runs because the deadline class has the highest precedence among these threads. Suppose it consumes its reserved runtime and becomes throttled. The real-time class now wins, and F runs because priority 70 is higher than R's priority 30.
If F blocks, R runs. The fact that N has nice -20 does not move it above R; nice values do not cross the real-time class boundary.
When both real-time threads block, the fair class chooses among N, B, and I. N has a very favorable weight, B is treated as batch work, and I has extremely weak priority. Their exact execution order depends on fair-class state, not on the real-time priority numbers.
If all of these threads block or become ineligible, the kernel finally runs the CPU's internal idle thread.
This example also shows why a process viewer's single PRI column can be misleading. The displayed values may be transformed for presentation, and values from different scheduling classes do not form one meaningful application-level ranking.
Loading simulation...
The following commands inspect scheduler state without assigning real-time privileges.
To show one row per thread:
Useful fields include:
Common CLS values include TS for SCHED_OTHER, B for SCHED_BATCH, IDL for SCHED_IDLE, FF for SCHED_FIFO, RR for SCHED_RR, and DLN for SCHED_DEADLINE. Older versions of ps may display ? for a policy they do not recognize, even when the kernel supports it.
The chrt utility can query the current shell's policy:
A normal shell typically reports:
To query every thread belonging to a process:
To inspect the priority ranges recognized by the local system:
The exact policies shown depend on the installed kernel and chrt version.
You can also create short-lived batch and idle-policy processes without requesting real-time priority:
Typical class values are:
The processes are sleeping, so this experiment verifies policy assignment without creating CPU load. Some containers or restricted environments may reject policy changes even when the policy would normally be available.
Avoid experimenting with commands such as sudo chrt --fifo 99 ... on a machine you need to remain responsive. A nonblocking FIFO loop can prevent ordinary administrative tools from running. Linux commonly provides real-time bandwidth controls, but they are safeguards rather than permission to assign maximum real-time priority casually.
The current system's global real-time bandwidth settings can be inspected without changing them:
Their presence and values depend on kernel configuration. Changing them affects the whole system and should be treated as an administrative operation.
Linux provides several interfaces because its scheduling policies do not all use the same parameters.
nice() and setpriority() adjust niceness for fair-scheduled work. The shell commands nice and renice expose the same general control.
sched_setscheduler() and sched_setparam() configure traditional policies such as SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, and SCHED_RR. For the fair policies, the static scheduling priority must be zero. For FIFO and RR, it carries the real-time priority.
sched_setattr() is a Linux-specific extended interface. It is required for SCHED_DEADLINE because a single integer priority cannot represent runtime, deadline, and period.
POSIX thread programs commonly use pthread_setschedparam() when setting a thread's traditional policy and real-time priority.
Permission checks protect these interfaces. An unprivileged thread may use FIFO or RR priority only within limits such as RLIMIT_RTPRIO; creating a deadline reservation normally requires the CAP_SYS_NICE capability. Failure with EPERM is therefore expected when an unprivileged program attempts a protected change.
The thread-level nature of these APIs is important. Changing one thread does not automatically change every thread in its process.
Linux divides runnable threads among scheduling classes rather than placing every thread in one numeric priority queue. The kernel considers class precedence first, then uses the selected class's own policy rules.
SCHED_OTHER, SCHED_BATCH, and SCHED_IDLE belong to the fair class. SCHED_FIFO and SCHED_RR use fixed real-time priorities, while SCHED_DEADLINE uses admitted runtime, deadline, and period reservations. Optional sched_ext support allows a BPF scheduler to implement specialized behavior.
Policy values only make sense within their class: nice is not a real-time priority, FIFO is not equal-priority Round Robin, and user-visible SCHED_IDLE is not the kernel's idle class. These distinctions are essential when reading Linux scheduling tools or selecting a policy for an application.
5 quizzes