1. 响应优先级基础概念:线程调度模型、优先级继承、优先级反转

1.1 线程调度模型

Android Framework 采用 抢占式优先级调度,结合 CFS(完全公平调度)与实时调度类。线程调度模型决定哪个线程获得 CPU 以及能运行多久,直接影响触控响应、动画帧率和后台任务进展。

调度类调度策略优先级范围适用场景
SCHED_NORMAL (CFS)公平共享,nice 值调整权重100~139 (nice -20~19)普通应用线程、后台服务
SCHED_FIFO (实时)先进先出,直到被更高优先级抢占或主动 yield1~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

优先级反转与继承流程图 优先级反转与优先级继承示意图 高优先级线程 (如UI渲染线程) 中优先级线程 (如后台编译线程) 低优先级线程 (持有锁 L) 共享锁 L ⛔ 优先级反转阶段 1. 高优先级等待锁L(被低优先级持有) 2. 中优先级抢占低优先级,低优先级无法释放锁 3. 高优先级无限等待 → 系统抖动/ANR ✅ 优先级继承解决 系统临时将低优先级提升至与高优先级相同 低优先级得以不被中优先级抢占,快速释放锁 请求锁 L 锁被低优先级持有 抢占

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: 在普通线程中误用了 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 的唤醒延迟,超出则告警。