2. Linux 内核调度器基础:CFS 调度器原理、时间片、nice 值、优先级映射关系
在 Android 系统中,CPU 资源的分配由 Linux 内核的调度器负责。从 2.6.23 内核开始,完全公平调度器(CFS,Completely Fair Scheduler)成为默认调度器。CFS 的设计哲学是:为每个任务提供对 CPU 时间的“公平”分配,而不是依赖传统的时间片轮转。理解 CFS 是进行 Android 内核调度调优的前提。
2.1 CFS 调度器核心原理
CFS 摒弃了传统 Unix 调度器中固定时间片和优先级计算复杂度的做法。它基于一个简单的模型:在一个理想的、完全多任务处理的 CPU 上,每个任务应该获得 1/N 的 CPU 时间(N 为可运行任务数)。CFS 通过 虚拟运行时间(vruntime) 来逼近这个理想模型。
vruntime 是 CFS 为每个可运行任务维护的一个度量值,表示任务在 CPU 上实际运行的时间,但会根据任务的优先级(nice 值)进行加权。vruntime 越小,表示任务受到的“不公平”越多,因此越需要被调度执行。
CFS 使用红黑树(Red-Black Tree)来组织所有可运行的任务。红黑树是一棵自平衡的二叉搜索树,其键值就是任务的 vruntime。调度器每次选择树的最左节点(即 vruntime 最小的任务)来运行。这种设计使得调度决策的时间复杂度为 O(log N),非常高效。
2.2 时间片与调度周期
CFS 没有传统意义上的固定时间片。它引入了 调度周期(sched_latency) 的概念。调度周期是 CFS 保证每个可运行任务至少运行一次的时间窗口。在这个周期内,所有任务按权重比例分享 CPU 时间。
每个任务获得的时间片长度由以下公式决定:
任务时间片 = 调度周期 × (任务权重 / 所有可运行任务总权重)
当可运行任务数量增多时,如果仍然使用固定的调度周期,每个任务获得的时间片会变得非常小,导致上下文切换开销过大。因此,CFS 引入了 最小粒度(min_granularity) 的概念。当计算出的时间片小于最小粒度时,CFS 会按比例放大调度周期,以确保每个任务至少获得最小粒度的运行时间。
| 参数 | 默认值(Android 常见内核) | 说明 |
|---|---|---|
| sched_latency_ns | 6,000,000 (6ms) | 目标调度周期。任务数少时,所有任务在此周期内至少运行一次。 |
| sched_min_granularity_ns | 750,000 (0.75ms) | 任务运行的最小时间片。防止上下文切换过于频繁。 |
| sched_wakeup_granularity_ns | 1,000,000 (1ms) | 唤醒抢占粒度。控制被唤醒任务能否抢占当前运行任务。 |
sched_min_granularity_ns,让调度器更频繁地切换回 UI 线程,减少卡顿。但过小的值会增加上下文切换开销。
2.3 nice 值与优先级映射
在 Linux 中,用户态通过 nice 值 来影响任务的优先级。nice 值的范围是 -20 到 +19,默认值为 0。nice 值越小,优先级越高;nice 值越大,优先级越低。
CFS 不直接使用 nice 值,而是将其映射为 权重(weight)。权重决定了任务在调度周期内获得 CPU 时间的比例。内核维护了一个从 nice 值到权重的映射表。
内核中实际的权重映射表(位于 kernel/sched/core.c)如下:
static const int prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
2.4 优先级映射关系:从 nice 到调度实体
CFS 将 nice 值映射为权重后,进一步通过 调度实体(sched_entity) 来管理每个任务。调度实体是 CFS 调度的基本单位,它包含了 vruntime、权重等关键信息。
整个映射链路如下:
- 用户态设置 nice 值:通过
setpriority()系统调用或renice命令。 - 内核转换为静态优先级:
static_prio = MAX_RT_PRIO + nice + 20(其中 MAX_RT_PRIO 通常为 100)。 - 静态优先级映射为权重:通过
prio_to_weight[]数组查找。 - 权重影响 vruntime 增长速率:
vruntime += 实际运行时间 × (NICE_0_LOAD / 任务权重)。权重越大,vruntime 增长越慢,任务越容易被调度器选中。
2.5 与 Android 性能调优的关联
在 Android 系统中,理解 CFS 的这些基础机制对于以下场景至关重要:
- UI 线程响应优化:通过调整 UI 线程的 nice 值(通常设为 -2 或 -3),使其获得更大的权重,从而在 CPU 竞争中获得更多时间片,减少渲染卡顿。
- 后台任务限制:将后台批量任务(如日志同步、数据备份)的 nice 值调高(如 +5 到 +10),降低其对前台交互的影响。
- 驱动中断线程化:对于需要快速响应的驱动,可以将其中断处理线程的 nice 值设为负值,确保其能抢占普通用户态任务。
- cgroup 与调度组:Android 使用 cgroup 对进程分组,CFS 的调度实体可以是单个任务,也可以是一个任务组。通过
cpu.shares可以控制组间的 CPU 分配比例。
| 概念 | 对 Android 性能的影响 |
|---|---|
| vruntime | 决定下一个运行的任务。UI 线程需要较小的 vruntime 以快速获得 CPU。 |
| 调度周期 | 影响任务切换频率。过短增加开销,过长导致响应延迟。 |
| nice 值 / 权重 | 直接控制 CPU 资源分配比例。是调优的主要手段。 |
| 最小粒度 | 防止任务切换过于频繁。对于微秒级响应的驱动,可能需要调小此值。 |