AOSP Framework & Internals
6 min read

The CPU Scheduler

Discover how the Linux kernel distributes processing time across multiple CPU cores using the Completely Fair Scheduler.

Your Android phone has eight physical processing cores, but at any given moment, hundreds of threads are demanding execution. The kernel faces a brutal mathematical challenge. It must constantly pause and resume threads, swapping them onto the physical cores thousands of times per second. Giving every thread a rigid time slice fails spectacularly when system load spikes.

Linux solves this problem with the Completely Fair Scheduler. Instead of fixed time quotas, this scheduler tracks the exact amount of CPU time each thread consumes. It organizes every runnable thread into a highly efficient binary search tree called a Red-Black Tree. Threads are ordered by their consumed virtual runtime, known as vruntime.

The execution rule is elegantly simple. The thread sitting on the far left of the tree has received the least amount of CPU time. The scheduler picks that left-most thread and lets it run on a core. As the thread executes, its virtual runtime increases. When it pauses, it is inserted back into the tree further to the right.

This visualizes the Red-Black Tree to clarify how the kernel makes its scheduling decisions. You will see how threads with lower virtual runtime migrate leftward. Notice how the scheduler always picks the left-most node for the next execution cycle.

The scheduler extracted the node at the bottom left because its runtime was lowest. Continuous sorting ensures no thread starves while preventing aggressive threads from hoarding the processor.

Priorities and Nice Values

Treating every thread exactly the same creates a terrible user experience. The active game you are playing needs instant CPU access, while an email client syncing in the background can wait. If the scheduler treated them equally, your game would stutter every time an email arrived.

Android influences the scheduling tree using Nice values. A Nice value ranges from negative twenty to positive nineteen. Lower numbers mean the process is less nice to others and demands a larger share of CPU time. Higher positive numbers indicate the process willingly yields the processor to other tasks.

The ActivityManager dynamically adjusts these values as you navigate your phone. When you minimize an application, the system increases its Nice value to a high number like nineteen. This drastically reduces the background application's CPU allocation. Bringing an application back to the foreground drops its Nice value, instantly granting it priority execution.

Debugging priority issues is difficult because these background adjustments are entirely invisible. You can observe these values directly using the top command. This tool prints the current state of processes running on your device. Expect to see a live table of threads, and look specifically for the NI column in the output.

adb shell top -n 1

Background processes carry high positive numbers, while critical system daemons often sit in the negative range.

Common Mistake: Engineers often assume negative nice values guarantee immediate execution. They only grant a larger proportional share of CPU time within the tree, not absolute dominance over the core.

Real-Time Scheduling

For highly sensitive tasks, the continuous math of the fair scheduler takes too long. Audio mixing requires strict millisecond precision to prevent crackling sounds in your headphones. Waiting in the Red-Black tree introduces unacceptable latency, even with a highly favorable Nice value.

Android bypasses the standard scheduler entirely for these workloads using Real-Time scheduling policies. A real-time thread ignores the tree and instantly preempts any normal application currently running on a core. The kernel drops whatever it was doing to service the real-time thread immediately.

Because a rogue real-time thread can permanently freeze the entire phone, the kernel heavily restricts this power. Only highly privileged system daemons possess the permissions to use this policy. The audio server is a primary example of a trusted component with this capability.

This sequence diagram illustrates how a Real-Time thread bypasses standard tasks. You will see a normal application running until a high-priority audio frame arrives. Notice how the kernel instantly pauses the application to service the audio thread.

The background application has no control over this interruption. The kernel enforces the real-time priority, ensuring the audio buffer fills before the user ever hears an audio glitch.

Energy-Aware Scheduling

Original Linux schedulers targeted server racks where every CPU core is physically identical. Mobile devices use asymmetrical architectures instead. A modern mobile processor mixes massive power-hungry cores with small battery-efficient cores. Traditional schedulers would blindly assign lightweight background work to a massive core, draining the battery in hours.

Android drove the development of Energy-Aware Scheduling to teach the kernel about hardware asymmetry. This subsystem mathematically predicts how much energy a thread will consume based on its historical load. The system builds an energy model of the physical hardware, calculating the exact wattage required to run a specific task on a specific core.

When a thread wakes up, the system evaluates this energy model. It places lightweight background tasks exclusively on the small cores to preserve battery life. The massive cores remain asleep until you touch the screen, launch a heavy application, or start rendering graphics.

By mapping tasks to the most efficient silicon available, this model delivers peak performance during interactions while stopping battery drain when the device sits in your pocket. The CPU scheduler does more than just share time. It actively manages the thermal and electrical limits of the entire phone.

These scheduling decisions dictate how quickly your application wakes up when a user taps its icon. But what actually happens when you press that icon? The framework must spawn a completely new process from scratch.