3. 线程优先级与调度策略:SCHED_FIFO/SCHED_RR/SCHED_OTHER 区别、优先级数值映射、nice 值与实时优先级转换

3.1 调度策略分类与特性

Linux 内核为线程提供了三种主要的调度策略,它们决定了线程在 CPU 上的运行顺序和时间分配。RTL 场景下,SystemUI 的触摸响应、动画渲染等关键线程必须使用实时策略才能保证低延迟。

调度策略类型优先级范围时间片适用场景
SCHED_OTHER普通分时nice -20 ~ 19动态分配(CFS)后台任务、非实时线程
SCHED_FIFO实时(先进先出)实时优先级 1~99无时间片,阻塞或主动让出硬实时、高确定性任务
SCHED_RR实时(轮转)实时优先级 1~99固定时间片(默认 100ms)同优先级实时任务轮转
关键区别: SCHED_FIFO 线程一旦运行,除非被更高优先级实时线程抢占或主动让出 CPU,否则会一直运行;SCHED_RR 在同一优先级内按时间片轮转,防止某个线程长时间霸占 CPU。Android 原生 SystemUI 使用 SCHED_OTHER,RTL 优化需将关键线程提升至 SCHED_FIFO。

3.2 实时优先级数值映射

实时优先级范围 1~99(数值越大优先级越高)。普通线程通过 nice 值映射到 100~139 的静态优先级,但实时优先级与 nice 值完全独立。调度器选择线程时首先比较调度策略:实时线程始终优先于普通线程运行。

// 实时优先级 99 为最高,1 为最低
// 普通优先级由 nice 值计算:prio = 120 + nice(nice 范围 -20~19)
// 例如 nice=-20 → prio=100(最高普通优先级)
// 实时线程的 prio 范围为 0~99,直接对应实时优先级

映射规则:

  • 实时线程:sched_priority = 1~99,对应内核 prio = 99 - sched_priority(内核中数值越小优先级越高)
  • 普通线程:nice = -20~19,对应内核 prio = 120 + nice
  • Android 使用 android.os.Process.setThreadPriority(int priority) 时,优先级参数 -20~19 直接映射为 nice 值,不会创建实时线程

3.3 nice 值与实时优先级转换

传统 nice 值只能调整普通调度策略的相对优先级,无法进入实时域。RTL 调优中需要通过 sched_setscheduler() 系统调用直接设置调度策略和实时优先级。

#include <sched.h>
#include <sys/resource.h>

// 设置当前线程为 SCHED_FIFO,优先级 80
struct sched_param param;
param.sched_priority = 80;
if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
    // 需要 CAP_SYS_NICE 权限
    perror("sched_setscheduler");
}

// 读取当前调度策略和优先级
int policy;
sched_getparam(0, &param);
policy = sched_getscheduler(0);
权限陷阱: 只有 root 或具有 CAP_SYS_NICE 能力的进程才能设置实时调度策略。SystemUI 作为系统进程默认拥有该能力,但应用层进程需要 android:sharedUserId="android.uid.system" 或通过 Service 提权。RTL 项目中通常将线程优先级设置逻辑放在 SystemUI 的 Binder 服务端。

3.4 调度策略选择流程与调试命令

实际调优时,按以下流程确定每个关键线程的策略与优先级:

  1. 识别 RTL 关键线程:InputReader、TouchAnimator、Choreographer 等
  2. 设定为 SCHED_FIFO,优先级 90~95(留出空间给更高优先级的系统中断)
  3. 非关键线程保持 SCHED_OTHER,通过 nice 值微调
  4. 使用 schedtool 或 chrt 命令行工具在线验证
# 查看线程调度策略和优先级(PID 1234)
adb shell "cat /proc/1234/sched | grep -E '(policy|prio)'"

# 使用 chrt 设置实时优先级(需要 root)
adb shell "chrt -f -p 90 1234"

# 查看所有线程的调度策略(-e 显示所有线程)
adb shell "chrt -p -e 1234"

# 查看可用优先级范围
adb shell "chrt -m"

3.5 优先级继承与反转风险

当高优先级实时线程等待低优先级线程持有的锁时,会发生优先级反转。RTL SystemUI 中应使用支持优先级继承的互斥锁(PTHREAD_PRIO_INHERIT)来避免。

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);
最佳实践: 实时优先级不是越高越好。Android 内核中中断处理线程优先级通常为 99,建议 SystemUI 线程使用 80~90,避免干扰中断响应。同时使用 perf sched 或 trace-cmd 录制调度事件,验证是否有线程被饿死或优先级反转。
线程优先级与调度策略决策流程 识别 RTL 关键线程 需要实时调度? 是 否 SCHED_FIFO / SCHED_RR 优先级 80~95 设置优先级继承互斥锁 SCHED_OTHER nice 值 -2 ~ 0 默认互斥锁即可 验证:perf sched / chrt 调优完成,持续监控 注意 SCHED_FIFO 优先级 不要超过 95,避免 与中断优先级冲突

3.6 常见错误与稳定性优化

  • 将 SCHED_FIFO 用于所有线程: 导致普通线程饥饿,系统响应下降。应仅用于触摸、动画、输入等关键路径。
  • 优先级设定过高(≥96): 可能阻塞软中断 ksoftirqd,引起网络/IO 延迟。
  • 忘记设置优先级继承: 实时线程等待普通线程锁时,优先级反转持续数秒。
  • 使用 setThreadPriority() 设置实时优先级: Android SDK 不直接暴露实时调度,必须通过 JNI 调用 sched_setscheduler 或使用 android.os.Process.setThreadScheduler()(Android 12+ 隐藏 API)。
稳定性验证命令: 部署后使用 stress --cpu 8 模拟高负载,同时用 logcat -b events | grep "am_anr" 监控 ANR,确保实时线程在负载下仍能正常调度。