4. CPU调频与调度协同:cpufreq、schedutil governor、负载跟踪(PELT/WALT)
CPU调频(DVFS)与任务调度是Android性能优化的两个核心杠杆。传统方案中,调频与调度各自为政,导致响应延迟或功耗浪费。本章深入剖析Linux内核中cpufreq子系统、schedutil governor以及负载跟踪机制(PELT/WALT)如何协同工作,实现“调度感知调频”,这是现代Android内核性能优化的基石。
4.1 cpufreq子系统架构
cpufreq是Linux内核中管理CPU频率和电压的核心框架。它通过governor(调控器)决定何时调整频率,并通过driver(驱动)操作硬件寄存器。
4.1.1 关键组件与数据流
| 组件 | 角色 | 典型实现 |
|---|---|---|
| cpufreq_policy | 管理单个CPU簇(cluster)的频率范围、可用governor、关联CPU | 每个性能域一个policy |
| cpufreq_governor | 根据CPU负载或调度提示,计算目标频率 | schedutil, interactive, ondemand |
| cpufreq_driver | 封装硬件操作:设置频率、查询可用频率表 | cpufreq-dt, acpi-cpufreq |
| cpufreq_stats | 记录各频率驻留时间,用于功耗统计 | /sys/devices/system/cpu/cpuX/cpufreq/stats |
4.2 schedutil governor:调度感知调频
schedutil 是 Linux 内核 4.7+ 引入的 governor,它直接与 CFS 调度器集成,利用调度器维护的负载跟踪数据(PELT/WALT)来驱动频率决策,消除了传统 governor 的采样延迟。
4.2.1 工作原理
- 负载更新事件驱动: 每次调度 tick 或任务唤醒/睡眠时,调度器更新 CPU 运行队列的负载。
- 频率计算: schedutil 在调度器更新负载后,立即根据当前负载和可用频率表计算目标频率。
- 快速切换: 通过
cpufreq_driver->fast_switch接口,在原子上下文中直接切换频率,避免工作队列延迟。
4.2.2 关键参数与调优
| 参数 | 路径 (sysfs) | 说明 | 推荐值 (Android) |
|---|---|---|---|
| up_rate_limit_us | .../schedutil/up_rate_limit_us | 频率提升的最小间隔,防止频繁升频 | 500-1000 |
| down_rate_limit_us | .../schedutil/down_rate_limit_us | 频率降低的最小间隔,防止频繁降频 | 2000-5000 |
| iowait_boost_enable | .../schedutil/iowait_boost_enable | 是否在 IO 等待时临时提升频率 | 1 (开启) |
4.3 负载跟踪:PELT vs WALT
负载跟踪是 schedutil 的“眼睛”。Android 内核主要使用两种负载跟踪算法:PELT(Per-Entity Load Tracking)和WALT(Window-Assisted Load Tracking)。Google 从 Android 10 开始逐步从 WALT 迁移回 PELT,但许多厂商仍在使用 WALT 或混合方案。
4.3.1 PELT(Per-Entity Load Tracking)
PELT 是主线 Linux 内核的标准负载跟踪算法。它通过半衰期衰减的数学公式,跟踪每个调度实体(task 或 runqueue)的负载贡献。
- 核心公式:
L = L * y + delta,其中y = 0.5^(1/32),即每 32ms 负载衰减一半。 - 优点: 数学优雅,时间连续,主线内核原生支持,维护成本低。
- 缺点: 对短时突发负载(如触摸点击)反应不够灵敏,因为衰减曲线需要时间爬升。
4.3.2 WALT(Window-Assisted Load Tracking)
WALT 是 Qualcomm 为移动设备开发的负载跟踪算法,被 Google 用于 Android 内核(android-4.4 至 android-10)。
- 核心思想: 将时间划分为固定窗口(通常 20ms),统计窗口内任务的执行时间。
- 优点: 对短时突发负载响应极快,一个窗口内即可反映负载变化,非常适合交互式场景。
- 缺点: 窗口边界效应导致负载抖动,多窗口累积逻辑复杂,与主线内核差异大,合并上游补丁困难。
4.3.3 对比总结
| 特性 | PELT | WALT |
|---|---|---|
| 时间模型 | 指数衰减,连续时间 | 固定窗口,离散时间 |
| 突发响应 | 较慢(需多个 decay 周期) | 快(一个窗口内即可反映) |
| 负载稳定性 | 平滑,抖动小 | 窗口边界处可能抖动 |
| 主线兼容性 | 高(主线原生) | 低(Android 特有) |
| 功耗表现 | 频率变化较平缓,功耗更优 | 频率可能频繁跳变,功耗略高 |
| Android 现状 | Android 10+ 默认 | Android 9 及之前默认,部分厂商继续使用 |
4.4 调度与调频的协同机制
schedutil 与 CFS 调度器的深度协同,体现在以下几个关键路径:
4.4.1 任务唤醒路径
当一个任务被唤醒(如触摸中断处理完成),调度器会调用 enqueue_task_fair,更新运行队列负载。schedutil 的钩子函数 cpufreq_update_util 被触发,立即计算并设置更高的频率,为即将执行的任务做好准备。
4.4.2 IO 等待 boosting
当任务因 IO 阻塞而睡眠,唤醒后 schedutil 会临时提升频率(iowait boost),帮助任务快速处理 IO 完成后的数据。这对于 Android 的存储性能至关重要。
iostat)进行微调。
4.4.3 迁移与簇间协同
在大小核架构(big.LITTLE)中,任务可能在不同簇之间迁移。schedutil 需要与 Energy-Aware Scheduling (EAS) 配合,确保任务迁移后,目标簇的调频策略能快速响应新任务的负载。
4.5 实战:Android 平台调优建议
- 确认当前使用的 governor:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor - 确认负载跟踪算法:检查内核配置
CONFIG_SCHED_WALT是否开启 - 调整 schedutil 的 rate_limit_us 参数,平衡响应速度与频率稳定性
- 监控
/proc/sched_debug和/sys/kernel/debug/sched_features中的负载信息
理解 cpufreq、schedutil 和负载跟踪的协同关系,是进行 Android 内核 CPU 调优的前提。下一层,我们将深入 IO 路径,探索存储性能的优化之道。