🔨 整理中 · 这一篇讲 Linux 的实时(RT)调度——
SCHED_FIFO/SCHED_RR两种实时策略,以及内核为防 RT 任务霸占 CPU 而设的带宽限制(RT throttling)。素材从读书笔记 ch10 + 6.19 源码整理。它是 01 调度器框架 里"rt_sched_class比fair优先级高"的展开,和 02 CFS/EEVDF 是配对的"另一类调度类"。
做什么
框架那篇 我们说 pick_next_task 沿调度类优先级链从高到低问,RT 类(rt_sched_class)排在 fair 之上——所以只要系统里有就绪的 RT 任务,fair 的普通任务就靠边。哪些任务算 RT?用了 SCHED_FIFO 或 SCHED_RR 策略、带 1~99 实时优先级的任务。这一篇拆 RT 调度类内部:它的优先级怎么组织、FIFO 和 RR 两种策略的区别、用什么数据结构 O(1) 选最高优先级任务,以及最关键的——内核怎么防止一个失控的 RT 任务把整台机器锁死(带宽限制)。理解它,你就知道为什么写 RT 程序要谨慎、以及 chrt 设了优先级后系统行为怎么变。
要了解什么
一、RT 优先级:1~99,nice 之外的另一套
普通任务用 nice 值(-20~+19)在 fair 类里调权重;RT 任务用实时优先级(1~99,数字越大越猛),在 rt 类里另起炉灶。这两套优先级不在一个竞技场——框架 的 for_each_class 保证只要 rt 类有就绪任务,fair 类根本轮不到。所以一个优先级 1 的 RT 任务,能压过所有 nice -20 的普通任务。
二、SCHED_FIFO vs SCHED_RR:有无时间片
两种 RT 策略的区别只在"同优先级之间怎么轮":
SCHED_FIFO(先进先出):没有时间片。一个 FIFO 任务上了 CPU,只要它不让出(不阻塞、不sched_yield)、不被更高优先级打断,它就一直跑下去。同优先级的其它 FIFO 任务只能干等。这意味着 FIFO 容易让同优先级任务饿死——一个死循环 FIFO 任务能把同优先级的伙伴永远晾着。SCHED_RR(轮转):有时间片(默认 100ms)。同优先级的 RR 任务轮流上,谁时间片用完就排到该优先级队列末尾、换下一个。比 FIFO 友好,适合"几个同等重要的 RT 任务需要并发"。
两者的"抢占"规则一致:更高 RT 优先级的任务一就绪,立刻抢占当前在跑的。这是"实时"的本质——可预测、可抢占。
三、rt_rq + rt_prio_array😮(1) 选最高优先级
RT 调度类的运行队列 struct rt_rq(kernel/sched/sched.h:826)内嵌一个 struct rt_prio_array active(:827),这是 RT 选任务的精巧数据结构:
struct rt_prio_array { /* sched.h:308 */
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* 位图:第 i 位=1 表示优先级 i 有任务 */
struct list_head queue[MAX_RT_PRIO]; /* 每个优先级一个链表头 */
};选下一个任务时,内核用 find_first_bit(bitmap) 一次找到"最高的非空优先级"(O(1)),再从那个优先级的 queue[i] 链表头取一个(O(1))——所以 RT 选任务是真正的 O(1),和优先级数无关(老 O(1) 调度器就是这套思路,fair 才改用红黑树/EEVDF)。FIFO 任务从队首取、跑完排回队首(让同优先级接着等);RR 任务时间片用完排到队尾(轮流)。
四、RT 带宽限制:防止 RT 霸占
SCHED_FIFO 没时间片,意味着一个失控的 RT 任务能占住 CPU 永远不让——系统其它部分(包括内核自己的维护任务、softdog)饿死,整台机器看起来就"死"了。为防这种灾难,内核给 RT 设了带宽限制(RT throttling):RT 任务在一个 period 内最多只能跑 runtime 时间,超了就被强制限流、把 CPU 让给其它任务。
sysctl_sched_rt_period(kernel/sched/rt.c:18,默认1000000us = 1s):统计周期sched_rt_runtime(默认950000us = 0.95s):周期内 RT 总共能跑的上限
默认配比 950000/1000000 = 95%——RT 任务最多占 95% CPU,留 5% 给系统维护/调度器自己喘气,防止"RT 失控 → 系统假死、连 shell 都进不去"。可调(/proc/sys/kernel/sched_rt_period_us/sched_rt_runtime_us),设 -1 关闭限流(慎用,适合你能保证 RT 任务行为可控的场合)。这套机制是 Linux 作为 GPOS(通用 OS)对"RT 失控"的最后防线——真硬实时场景需要 PREEMPT_RT 补丁,那是另一个话题。
五、给任务设 RT 策略:chrt 与内核里的 sched_set_fifo
用户态用 chrt -f -p 50 <pid>(FIFO 优先级 50)或 chrt -r -p 50 <pid>(RR)设任务策略。内核侧,5.9 起模块里不再用 sched_setscheduler 随便塞优先级,改用三个受限封装(框架那篇 提过):sched_set_fifo(p)(FIFO 优先级 50)、sched_set_fifo_low(p)(优先级 1)、sched_set_normal(p, nice)。社区认为"模块不该凭空挑实时优先级"——优先级值反映系统设计,该由系统集成者/用户空间定。
动手试试
- 宿主机上
chrt -p $$看当前 shell 的策略(应该是SCHED_OTHER/nice 0);chrt -f -p 50 $$把 shell 切成 FIFO 50(⚠️ 别在远程 ssh 会话里玩,可能锁死自己) - 写一个死循环程序,
chrt -f -p 99给它最高 FIFO 优先级,单核上观察它把 CPU 吃满 95%(RT throttling)后让出 5%——直观看到带宽限制 - 起两个同优先级 FIFO 死循环,观察只有一个在跑(另一个饿死);改成
chrt -r,两个轮流(各约 50%) - 读
kernel/sched/rt.c:2575的DEFINE_SCHED_CLASS(rt),对照 框架 的pick_next_task理解 RT 怎么被选 - 思考题:为什么 RT throttling 默认留 5% 给非 RT?(提示:系统维护、shell 进得去、softdog 不咬)
延伸阅读
- 源码(本仓库
third_party/linux/,6.19.9):kernel/sched/sched.h:308 struct rt_prio_array、:826 struct rt_rq、:109 sysctl_sched_rt_period;kernel/sched/rt.c——:18 sysctl_sched_rt_period(默认值)、:12 max_rt_runtime/MAX_BW、:68 init_rt_rq、:2575 DEFINE_SCHED_CLASS(rt)、RT throttling 在sched_rt_runtime_exceeded/do_balance_runtime;RT 调度策略 APIsched_set_fifo等在kernel/sched/syscalls.c。 - 关联本站:本篇是 01 调度器框架
for_each_class里 RT 类的内部展开;普通任务的 fair 类见 02 CFS/EEVDF;"按 bug 类型选工具"里的 softdog/lockup 见 debugging/00 调试全景图。 - 读书笔记:ch10 讲 POSIX 调度策略与优先级层级。