4. CPU 调度延迟分析:调度器、C-State、频率缩放、中断负载、RT线程抢占、Scheduling延迟
4.1 现象识别:Systrace 中的调度失调迹象
CPU 调度延迟在 Systrace 中的典型“指纹”是:一个任务已经处于 Runnable 状态(在调度跟踪中表现为橙色/红色区块或 Sched 与 wakeup 标记之间的 gap),但迟迟没有获得 CPU 时间片。具体可观察以下迹象:
- Sched 与 Wakeup 间 gap 大:选中任务后,查看
Scheduling wakeup latency或Runnable to Running时间 > 20ms。 - Running 段短但间隔长:任务频繁被换出,每次 Running 时长 < 1ms,但 Sched 到下次 Running 间隔 > 50ms。
- CPU 频率过低:在延迟区间内,CPU 频率柱状图显示工作在最低档(如 300MHz),同时有
cpufreq事件。 - 中断风暴集中在一个核:所有
irq/softirq都扎堆在 CPU0,而目标任务被调度到该核上排队。 - RT 线程压制:发现
sched_rt_rq或RT优先级任务连续运行 > 20ms,阻塞 CFS 任务。 - C-State 退出过长:在 idle 后首次调度前,有
cpu_idle: state=4且 exit_latency > 10ms。
快速识别规则:若选中一个 Runnable 线程后,
Waiting for CPU 时间占比超过总延迟的 60%,则属于典型调度延迟,优先排查本模块。
4.2 数据采集:抓取完整调度决策上下文
仅靠默认的 sched_switch 不够,必须开启调度器、频率、idle 和中断的细粒度 tracepoint。使用以下参数录制:
# 抓取典型调度延迟场景(以 Android 为例)
systrace.py --time=10 -o scheduling.html \
sched freq idle irq power workq
# 或使用 atrace 直接指定
atrace --async_start --buffer_size 16384 \
-t sched freq idle irq power workq gfx input view
# 确定延迟区间后,用 CLI 导出原始 atrace 文本
atrace --async_dump > /data/local/tmp/trace_raw.txt
在 Trace View 中增加以下的关键列(自定义显示):
| 跟踪项 | 意义 | 默认开启 |
|---|---|---|
cpufreq_interactive_target |
频率调速器目标频率 | 否 |
cpu_idle |
C-State 进入/退出事件 | 否 |
sched_wakeup_new |
新建任务的首次唤醒 | 是 |
sched_wakeup |
任务被唤醒时间点 | 是 |
sched_switch |
上下文切换 | 是 |
irq_handler_entry |
中断服务入口 | 否 |
采样技巧:针对周期性延迟问题,在 Systrace 中直接搜索
sched_wakeup 后紧接着 sched_switch(目标 PID)之间的时间戳差。若该差 > 10ms 且 CPU 不是 0 号核,优先怀疑中断亲和性和RT 抢占。
4.3 原因定位:调度延迟故障树排查表
使用以下故障树判断路径,依据 Systrace 事件逐层下钻定位根因:
结合故障树,按以下排查表逐个检验根因:
| 可疑根因 | Systrace 定界事件 | 判断规则 |
|---|---|---|
| 频率缩放延迟 | cpufreq_interactive_target 前后 gap 大,频率档位明明已提高但任务未运行 |
查看 cpufreq_interactive_target 与 sched_switch 之间的延迟,若 > 5ms 且无其他任务运行,则为频率切换慢。 |
| C-State 退出延迟 | cpu_idle: state=4 到 sched_switch 时间 > 8ms |
检查实际 exit_latency 是否匹配硬件规格;若 C-State 3+ 退出时没有先降频,则可能是 PMC 或 BIOS 配置。 |
| RT 线程抢占 | sched_rt_rq 事件中显示 RT 任务运行时长 > 20ms,且 CFS 任务在等待 |
计算 RT 任务连续占用 CPU 的时间,若 > 40ms 则必须降低其优先级或将 RT 任务限制到专用小核。 |
| 中断亲和性不均 | 所有 irq_handler_entry 出现在 CPU0,且 CPU0 上 CFS 任务等待时间最长 |
查看 interrupts 跟踪或 /proc/interrupts 分布;优先将网络/GPU 中断移到其他核。 |
| Scheduling 延迟(调度器自身) | sched_switch 调用前无其他任务运行,但 next_prio 却高于当前任务 |
启用 sched_stat_wait 查看等待队列长度;CFS 的 min_granularity 过大时会延迟任务被选中。 |
4.4 修复动作
根据定位到的具体分支执行修复:
- 频率缩放优化:修改
cpufreq调速器从interactive切换为schedutil;若系统不支持则调小hispeed_freq和target_loads。示例:
# 切换到 schedutil(基于调度器提示的调频)
echo schedutil > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 调高通调频的响应性(interactive 调速器下)
echo "80 1000000:90 1600000:95" > /sys/devices/system/cpu/cpufreq/interactive/target_loads
- C-State 控制:限制深度 C-State 的可用性,减少退出延迟:
# 禁止进入 C-State 3 或更深(调试用)
echo 2 > /sys/module/intel_idle/parameters/max_cstate # Intel
echo 1 > /sys/devices/system/cpu/cpu0/cpuidle/state3/disable # 通用
- 中断迁移:使用
smp_affinity将高频率中断从目标核移走:
# 查询中断号(以 eth0 为例)
cat /proc/interrupts | grep eth0 # 获取 IRQ 号,如 48
# 将 IRQ 48 的亲和掩码设为 0x00000002(仅 CPU1)
echo 2 > /proc/irq/48/smp_affinity
- RT 任务隔离:移除非关键 RT 任务到特定小核,或使用
chrt降低其优先级:
# 将 RT 任务的优先级降为 1(最弱)
chrt -r -p 1 $(pidof rt_task_name)
# 使用 cgroup/cpuset 将 RT 任务绑定到 CPU4-7
echo 0-3 > /dev/cpuset/rt/cpus
避坑提醒:大量设备默认关闭了
tickless 或 dynticks,导致 CPU 即使空闲也每秒多次被时钟中断唤醒,破坏 C-State。若 C-State 退出延迟高,先确认 CONFIG_NO_HZ_FULL 是否启用。
4.5 复测与验证
修改后必须重新抓取相同场景的 Systrace,并验证以下指标:
- Runnable to Running gap:目标任务的
Waiting for CPU时间从原来的 > 20ms 下降到 < 3ms。 - 频率响应曲线:在负载启动后的 2ms 内频率从最低档升到最高档,且
schedutil事件没有明显滞后。 - 中断分布:确认
irq_handler_entry不再全部集中在 CPU0,而是均匀分散(如 CPU0 20%、CPU1 25%、CPU2 25%、CPU3 30%)。 - C-State 退出次数:在负载区间内不应看到深 C-State(state > 2)退出;若仍然出现,继续调高
pm_qos_resume_latency_us限制。
# 验证中断分散情况
cat /proc/interrupts | head -n 10 # 检查第二列起 CPU 计数是否均衡
# 验证频率切换延迟(使用 trace 中的 time delta)
grep "sched_wakeup" /data/local/tmp/trace_raw.txt | head -n 20
4.6 预防与监控
在工程层面建立长期检查机制,避免同类延迟反复出现:
- 内核配置硬门控:在 defconfig 中强制开启
CONFIG_SCHEDUTIL、CONFIG_NO_HZ_FULL、CONFIG_IRQ_TIME_ACCOUNTING。 - 自动化回归脚本:每次 CI 跑性能测试时附带
trace-cmd record -e sched_switch -e cpu_frequency,并用脚本解析sched_wakeup延迟的 P95 百分位。 - 中断负载预警:在 dashborad 上监控
/proc/interrupts的方差,如果某个核的中断次数超过其他核 2 倍则告警。 - RT 任务白名单:建立 RT 任务列表,所有人新增 RT 任务时需 Code Review 确认其优先级和 cpuset,防止误用 RT 破坏 CFS 响应性。
长期监控点:推荐在性能看板上添加
调度延迟 P99 指标(Runnable→Running 时间),超过 5ms 则自动触发深度 Systrace 抓取,保留现场用于根因分析。