Understanding Linux Process Scheduling and Android Thread Management
The article explains why an Android app’s main thread can be blocked by detailing Linux process versus thread concepts, the Completely Fair Scheduler’s priority and vruntime calculations, real‑time and nice priorities, and how Android uses cgroups and SchedPolicy enums to allocate CPU shares among foreground, background, and system threads.
Recently, an APM system detected application jank using a Looper Printer similar to BlockCanary. The collected data showed that even though the main thread only ran for a few milliseconds, the UI was blocked for up to a second, indicating that the main thread failed to acquire CPU time. To investigate, the author examined Android thread scheduling in detail.
Linux Processes and Android Threads
Processes are the smallest units of resource management, while threads are the smallest units of program execution. A process must contain at least one thread, and it can contain many. On SMP machines, multiple threads can run on different CPUs simultaneously, improving parallelism. User‑level threads have lower context‑switch overhead than kernel threads.
Two main thread models exist: kernel threads (running in kernel mode) and user threads (implemented entirely in user space). Linux historically used LinuxThreads (1:1 model) before 2.6, after which NPTL (Native POSIX Thread Library) replaced it.
In Android, you can view threads of a process via adb shell ps -t -p -P <pid> or by inspecting /proc/<pid>/tasks (requires root).
Linux Process Scheduling
The scheduler decides which processes run and for how long. It balances CPU utilization and fairness, allowing higher‑priority or real‑time processes to pre‑empt lower‑priority ones.
Linux defines two priority schemes:
Normal priority (nice value, range -20 to 19). Lower nice → higher priority.
Real‑time priority (RTPRI, range 0‑99). Higher value → higher priority.
Dynamic priority adjusts a process’s static priority during execution to reward I/O‑bound or penalize CPU‑bound tasks.
Completely Fair Scheduler (CFS)
Since kernel 2.6.23, Linux uses CFS. It introduces virtual runtime (vruntime) to approximate ideal fair sharing. The scheduler always picks the task with the smallest vruntime. vruntime is updated as:
vruntime += delta * NICE_0_LOAD / se.weightwhere delta is the actual run time, se.weight is the weight derived from the task’s priority, and NICE_0_LOAD is a constant.
Time slices are allocated proportionally to the weight of all runnable tasks, not fixed per‑process values.
Cgroups in Android
Cgroups (control groups) limit, account, and isolate resource usage for groups of processes. Android uses cgroups for CPU, cpuset, and schedtune subsystems to differentiate foreground and background apps.
Key cgroup files:
/dev/cpuctl/cpu.shares – relative CPU share (e.g., 1024 for foreground, 52 for background).
/dev/cpuctl/cpu.rt_period_us and /dev/cpuctl/cpu.rt_runtime_us – real‑time runtime and period (e.g., 1 000 000 µs period, 800 000 µs runtime for foreground).
Example commands:
shell@hammerhead:/ $ cat /dev/cpuctl/cpu.shares
1024
shell@hammerhead:/ $ cat /dev/cpuctl/bg_non_interactive/cpu.shares
52These values imply a roughly 20:1 CPU share ratio between foreground and background groups.
SchedPolicy Definition
Android maps process groups to cgroups via the SchedPolicy enum defined in sched_policy.h :
typedef enum {
SP_DEFAULT = -1,
SP_BACKGROUND = 0,
SP_FOREGROUND = 1,
SP_SYSTEM = 2, // cannot be used with set_sched_policy()
SP_AUDIO_APP = 3,
SP_AUDIO_SYS = 4,
SP_TOP_APP = 5,
SP_CNT,
SP_MAX = SP_CNT - 1,
SP_SYSTEM_DEFAULT = SP_FOREGROUND,
} SchedPolicy;The set_sched_policy function selects the appropriate cgroup file descriptor based on the policy and writes the thread ID to it. Sample snippet:
int fd = -1, boost_fd = -1;
switch (policy) {
case SP_BACKGROUND:
fd = bg_cgroup_fd;
boost_fd = bg_schedboost_fd;
break;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
fd = fg_cgroup_fd;
boost_fd = fg_schedboost_fd;
break;
case SP_TOP_APP:
fd = fg_cgroup_fd;
boost_fd = ta_schedboost_fd;
break;
default:
fd = -1;
boost_fd = -1;
break;
}
if (add_tid_to_cgroup(tid, fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}Corresponding Java constants in Process.java (e.g., THREAD_GROUP_BG_NONINTERACTIVE = 0 ) map directly to the SchedPolicy values.
Overall, the article explains why the main thread may be blocked, the relationship between Linux processes and Android threads, the Linux scheduling mechanisms (nice, real‑time, CFS), and how Android leverages cgroups and SchedPolicy to enforce CPU resource allocation for different app categories.
iQIYI Technical Product Team
The technical product team of iQIYI
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.