4、RT调度器核心原理:优先级队列、FIFO与RR策略、实时优先级范围

实时(RT)调度器是Linux内核中与CFS调度器并列的核心调度组件,专门用于处理对响应时间有严格要求的实时任务。与CFS追求公平分配CPU时间不同,RT调度器遵循“优先级至上”的原则:优先级更高的实时任务总是优先于低优先级任务执行。本章深入剖析RT调度器的三大支柱:优先级队列的组织结构、FIFO与RR两种调度策略的差异,以及实时优先级的取值范围与映射规则。

4.1 实时优先级队列结构

Linux内核为每个CPU维护一个独立的实时运行队列(struct rt_rq),其核心是一个包含140个链表头的优先级位图数组。每个优先级对应一个双向链表,相同优先级的实时任务按照FIFO或RR规则排队。

关键数据结构: struct rt_prio_array 包含两个成员:bitmap(优先级位图,用于快速查找非空队列)和 queue(长度为MAX_RT_PRIO的链表数组)。
RT优先级队列结构(rt_prio_array) 优先级位图 (bitmap) — 140位,每位标记对应优先级链表是否为空 优先级链表数组 (queue[140]) prio 0 T1 T2 T3 … prio 50 T4 T5 prio 99 T6 prio 100 (空链表) prio 139 (空链表) 查找规则 • 从bitmap最低位开始扫描 • 找到第一个置1的位 → 最高优先级 • 从对应链表头部取任务执行
性能优势: 位图查找使用 __ffs() 指令(查找第一个置位位),时间复杂度为 O(1),与系统中实时任务数量无关。这使得RT调度器在大量实时任务场景下仍能保持极低的调度延迟。

4.2 FIFO与RR调度策略

Linux实时任务支持两种调度策略,通过 sched_setscheduler() 系统调用指定:

策略 调度标识 时间片 抢占行为 典型场景
SCHED_FIFO 先进先出 无时间片(运行到阻塞或主动让出) 仅被更高优先级任务抢占 中断处理、高精度定时器回调
SCHED_RR 时间片轮转 默认100ms(可调) 时间片耗尽后同优先级轮转 多媒体处理、周期性控制任务

4.2.1 SCHED_FIFO 调度规则

  • 运行条件: 一旦获得CPU,FIFO任务会持续运行直到发生以下任一事件:主动调用 sched_yield()、因I/O或同步原语阻塞、被更高优先级的实时任务抢占。
  • 同优先级处理: 多个相同优先级的FIFO任务按入队顺序依次执行,后唤醒的任务排在链表尾部。
  • 风险提示: 如果FIFO任务进入无限循环且没有更高优先级任务,它将永久霸占CPU,导致低优先级任务(包括CFS普通任务)完全饿死。
⚠ 设计陷阱: 新手常犯的错误是让FIFO任务执行长时间计算而不主动让出CPU。正确的做法是将实时任务设计为“短促执行+阻塞等待”模式,或使用SCHED_RR策略引入时间片约束。

4.2.2 SCHED_RR 调度规则

  • 时间片机制: 每个RR任务拥有固定长度的时间片(默认100ms,可通过 /proc/sys/kernel/sched_rr_timeslice_ms 调整)。
  • 轮转规则: 时间片耗尽后,任务被放到同优先级链表尾部,调度器选择链表头部的下一个RR任务执行。
  • 优先级交互: 如果更高优先级任务就绪,当前RR任务被抢占,但保留剩余时间片;恢复执行时继续使用剩余时间片。
SCHED_FIFO vs SCHED_RR 执行对比 SCHED_FIFO (prio=50) T1 持续运行 阻塞 T2 执行 T1运行直到主动阻塞 → T2获得CPU → T1唤醒后等待T2主动让出 SCHED_RR (prio=50, 时间片=100ms) T1 时间片1 T2 时间片1 T1 时间片2 T2 时间片2 T3 时间片1 每个任务轮流获得100ms时间片,时间片耗尽后自动切换到下一个同优先级任务 核心区别 FIFO:无时间片约束,运行到阻塞或主动让出 → 适合短促、不可中断的实时任务 RR:时间片轮转,自动切换 → 适合需要公平共享CPU的同优先级实时任务组

4.3 实时优先级范围与映射

Linux内核使用0~139的全局优先级数值,其中实时任务占据最高区间:

范围 调度类 用户可见优先级 内核内部优先级 说明
0 – 99 RT调度器 1 – 99(sched_get_priority_max/min) 0 – 99 数值越小优先级越高(内核视角)
100 – 139 CFS调度器 -20 – 19(nice值) 100 – 139 nice值越低优先级越高
用户态API映射: 用户通过 sched_setscheduler() 设置实时优先级时,传入的 param.sched_priority 范围为1~99。内核内部将其转换为 MAX_RT_PRIO - 1 - param.sched_priority,即用户传入99对应内核优先级0(最高),用户传入1对应内核优先级98。

4.3.1 优先级比较规则

  • 跨类比较: 任何实时任务(优先级0~99)的优先级都高于所有CFS普通任务(优先级100~139)。这意味着只要有一个实时任务处于就绪态,CFS任务就不会被调度。
  • 同策略比较: 数值越小优先级越高。内核优先级0的任务会抢占优先级50的任务。
  • 优先级继承: 当低优先级实时任务持有高优先级任务需要的锁时,通过优先级继承协议临时提升低优先级任务的优先级,防止优先级反转。
Linux全局优先级映射与调度类划分 0 50 99 120 139 RT调度器区域 优先级 0 – 99(内核内部) CFS调度器区域 优先级 100 – 139(nice -20 ~ 19) 用户态实时优先级:1 ~ 99 内核映射:MAX_RT_PRIO - 1 - user_prio 用户态nice值:-20 ~ 19 内核映射:MAX_RT_PRIO + 20 + nice 关键规则 1. 所有RT任务(0~99)优先级 > 所有CFS任务(100~139) 2. 内核优先级数值越小 → 调度优先级越高 3. 用户态实时优先级1最高,99最低(与内核视角相反)

4.4 实时任务调度流程

当调度器选择下一个任务时,RT调度器的执行流程如下:

  1. 检查位图: 调用 sched_find_first_bit() 扫描 rt_prio_array.bitmap,找到第一个置1的位,即当前最高非空优先级。
  2. 取链表头: 从 queue[prio] 链表的头部取出第一个任务。
  3. 策略判断: 如果任务是SCHED_FIFO,直接运行;如果是SCHED_RR,检查时间片是否耗尽,耗尽则移至链表尾部并取下一个任务。
  4. 上下文切换: 执行 context_switch() 切换到选中的任务。
调试技巧: 使用 chrt 命令查看和修改实时任务属性:
chrt -f -p 50 1234 # 将PID 1234设置为SCHED_FIFO,优先级50
chrt -r -p 99 5678 # 将PID 5678设置为SCHED_RR,优先级99
chrt -p 1234 # 查看PID 1234的当前调度策略和优先级

4.5 实时调度与CFS的协作边界

RT调度器和CFS调度器在同一个CPU上共存,通过以下机制保证协作:

机制 描述 配置接口
RT节流(RT Throttling) 限制实时任务在单位时间内的总执行时间,防止RT任务饿死CFS任务 /proc/sys/kernel/sched_rt_period_us(默认1秒)
/proc/sys/kernel/sched_rt_runtime_us(默认0.95秒)
优先级隔离 实时任务优先级始终高于CFS任务,CFS任务只在没有实时任务就绪时获得CPU 内核硬编码,不可配置
负载均衡 RT任务可以在多CPU间迁移,但迁移策略与CFS不同(更保守) /proc/sys/kernel/sched_rt_runtime_us 影响迁移决策
⚠ 生产环境建议: 在混合负载系统中,务必启用RT节流(默认已启用)。如果 sched_rt_runtime_us 设置为 -1,则RT任务可以无限期运行,可能导致CFS任务完全无法获得CPU,系统响应性急剧下降。