1. 响应优先级基础概念:线程调度模型、优先级继承、优先级反转
1.1 线程调度模型
Android Framework 采用 抢占式优先级调度,结合 CFS(完全公平调度)与实时调度类。线程调度模型决定哪个线程获得 CPU 以及能运行多久,直接影响触控响应、动画帧率和后台任务进展。
| 调度类 | 调度策略 | 优先级范围 | 适用场景 |
|---|---|---|---|
| SCHED_NORMAL (CFS) | 公平共享,nice 值调整权重 | 100~139 (nice -20~19) | 普通应用线程、后台服务 |
| SCHED_FIFO (实时) | 先进先出,直到被更高优先级抢占或主动 yield | 1~99 (实时优先级) | SurfaceFlinger、音频线程、Binder 关键服务 |
| SCHED_RR (实时) | 时间片轮转,同优先级轮流 | 1~99 (实时优先级) | 视频解码、Vulkan 渲染线程 |
| SCHED_BATCH | 批量调度,减少抢占次数 | 100~139 | 后台编译、dex2oat |
在 Framework 层,系统通过 sched_setscheduler() 系统调用设置线程调度策略。常见关键线程优先级如下:
// surfaceflinger.cpp 示例(伪代码)
struct sched_param param = { .sched_priority = 2 };
sched_setscheduler(thread_id, SCHED_FIFO, ¶m);
// Binder 线程默认使用 SCHED_NORMAL,但可通过 set_sched_policy 提升
const char* policy = "rt"; // 对应实时优先级
set_sched_policy(thread_id, SP_RT_PRIORITY);
调优要点: 实时优先级不宜滥用,过高的
SCHED_FIFO 线程会饿死普通线程。关键显示线程(如 SurfaceFlinger 主线程)建议使用 sched_priority ≤ 3,音频线程 ≤ 5。
1.2 优先级继承(Priority Inheritance)
优先级继承是一种用于解决优先级反转的协议。当一个低优先级线程持有了高优先级线程所需的资源时,系统会临时将低优先级线程提升到与高优先级线程相同的优先级,使它能尽快释放资源。
- 触发条件: 高优先级线程等待被低优先级线程持有的锁/信号量。
- 实现机制: 内核在
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT)开启,或通过futex实现。 - 恢复时机: 低优先级线程释放锁后优先级回退至原始值。
// JNI 层设置互斥锁优先级继承示例
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
1.3 优先级反转(Priority Inversion)
优先级反转是指一个高优先级线程被一个低优先级线程阻塞,而低优先级线程又被一个中等优先级线程抢占,导致高优先级线程无限期等待。典型的场景如下图所示。
经典案例: 1997 年火星探路者任务因优先级反转导致系统复位。在 Android 中同样可能发生:UI 线程等待 Binder 服务,而 Binder 服务线程被后台 dexopt 线程抢占,造成 App 无响应(ANR)。
1.4 三种场景对比
| 场景 | 现象 | 危害 | 解决方案 |
|---|---|---|---|
| 无反转 | 高优先级直接获取资源,低优先级正常执行 | 无 | — |
| 简单反转 | 高优先级等待低优先级释放锁,低优先级被中优先级抢占 | 延迟骤增,可能触发看门狗 | 优先级继承 / 优先级置顶协议 |
| 链式反转 | 多层嵌套锁导致多个优先级被反转 | ANR、系统卡死 | 锁设计精简 + 实时调度保护 |
1.5 内核配置与调试
- 内核 CONFIG:
CONFIG_RT_GROUP_SCHED、CONFIG_FUTEX、CONFIG_PREEMPT_RT(全抢占内核)。 - 调试命令:
# 查看所有线程调度策略与优先级
adb shell "ps -AT -eo tid,comm,sched,nice,prio | grep surfaceflinger"
# 实时跟踪线程优先级变更
adb shell "echo 1 > /proc/sys/kernel/sched_rt_runtime_us"
adb shell "cat /proc/sys/kernel/sched_rt_period_us"
- 日志分析: 捕获
sched_wakeup和sched_switch事件,通过trace-cmd分析反转时长。
adb shell trace-cmd record -e sched_switch -e sched_wakeup -T sleep 5
adb shell trace-cmd report trace.dat | grep "priority"
1.6 内联SVG:Thread Priority Inversion Flow
1.7 关键API与实现流程
Framework 层主要通过 libcutils 中的 set_sched_policy() 和 get_sched_policy() 管理线程优先级。实时线程必须显式加入实时线程组,否则即使设置 SCHED_FIFO 也会被限制。
// frameworks/native/libs/cutils/sched_policy.cpp
int set_sched_policy(pid_t tid, SchedPolicy policy) {
switch (policy) {
case SP_BACKGROUND:
sched_setscheduler(tid, SCHED_NORMAL, ¶m); // nice = 10
break;
case SP_FOREGROUND:
sched_setscheduler(tid, SCHED_NORMAL, ¶m); // nice = 0
break;
case SP_SYSTEM:
sched_setscheduler(tid, SCHED_NORMAL, ¶m); // nice = -2
break;
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
param.sched_priority = 5;
sched_setscheduler(tid, SCHED_FIFO, ¶m);
break;
case SP_TOP_APP:
param.sched_priority = 1;
sched_setscheduler(tid, SCHED_FIFO, ¶m);
break;
default:
return -EINVAL;
}
}
1.8 常见错误与优化策略
错误1: 在普通线程中误用了
修复: 从内核 proc 文件
错误2: 未开启优先级继承,造成高频反转。
修复: 检查所有跨优先级共享的
SCHED_FIFO 且未设置实时限制,导致系统 UI 卡顿。修复: 从内核 proc 文件
/proc/sys/kernel/sched_rt_runtime_us 检查实时线程总运行时间(默认 950ms/1000ms),确保实时线程总负载不超限。错误2: 未开启优先级继承,造成高频反转。
修复: 检查所有跨优先级共享的
pthread_mutex_t 是否设置了 PTHREAD_PRIO_INHERIT 协议。可通过 strace 追踪 futex(FUTEX_LOCK_PI) 确认。
- 优化建议: 减少锁粒度,尽量避免高优先级线程与低优先级线程共用同一锁;对关键路径使用 优先级置顶(Priority Ceiling) 协议:
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_PROTECT),设定锁的 ceiling 优先级为最高潜在请求者的优先级。 - 稳定性基线: 在持续集成中增加
Ftrace脚本,检测sched_switch中一个线程被阻塞时长超过 10ms 的唤醒延迟,超出则告警。