3、实时调度策略详解:SCHED_FIFO、SCHED_RR、SCHED_DEADLINE的原理与区别
聊到实时调度,很多人的第一反应就是「优先级高的先跑」。嗯,这话没错,但只说对了一半。Linux 内核里给实时任务准备了三种调度策略,各有各的脾气。我最早接触实时系统时,也以为 FIFO 和 RR 差不多,直到在项目中踩了坑,才真正理解它们的区别。
说白了,这三种策略解决的是同一个问题:谁先拿到 CPU,能跑多久,跑不完怎么办。但它们的答案完全不同。
3.1 SCHED_FIFO:先来先服务,不打断
FIFO 的全称是 First In First Out,翻译过来就是「先进先出」。但这里的「先进」不是指时间上的先后,而是指优先级。同一个优先级下,谁先进入就绪队列,谁就先跑。
它的核心规则很简单:
- 一个 SCHED_FIFO 任务一旦获得 CPU,就会一直运行,直到它主动让出(比如 sleep、等待 I/O、调用 sched_yield)
- 或者被更高优先级的 SCHED_FIFO/SCHED_RR 任务抢占
- 同优先级下,不会发生时间片轮转
关键点:SCHED_FIFO 没有时间片的概念。它跑起来就像一头倔驴,你不拉它它就不停。
我在项目中遇到过这样一个场景:一个数据采集任务用了 SCHED_FIFO,优先级设得挺高。结果它在一个循环里做密集计算,忘了加 sleep。好家伙,整个系统的其他实时任务全被饿死了,连键盘响应都卡顿。这就是典型的「FIFO 任务写死循环」的坑。
我曾经踩过的坑:写 SCHED_FIFO 任务时,一定要确保代码路径上有明确的阻塞点(比如 read、sleep、sched_yield)。否则一个 bug 就能让整个系统「冻住」。别问我怎么知道的。
3.2 SCHED_RR:带时间片的轮转
SCHED_RR 的全称是 Round Robin,也就是时间片轮转。它和 FIFO 的区别就一句话:同优先级下,大家轮流用 CPU。
具体规则:
- 每个 SCHED_RR 任务有一个默认的时间片(通常是 100ms,可调)
- 时间片用完后,任务被放到同优先级队列的末尾
- 如果被更高优先级任务抢占,时间片保留,下次继续跑
你想想看,这就像几个同事共用一台打印机。FIFO 是「谁先到谁用,用完才换人」,RR 是「每人打印 10 页,然后换下一个人」。RR 的好处是公平,坏处是实时性不如 FIFO 那么「硬」。
我个人习惯在需要多个同等重要的实时任务时用 RR。比如一个系统里有三个传感器数据采集任务,优先级相同,用 RR 就能保证每个都能定期拿到 CPU,不会出现某个任务被饿死的情况。
小技巧:时间片可以通过 /proc/sys/kernel/sched_rr_timeslice_ms 调整。但我建议别轻易改,除非你很清楚自己在做什么。默认值通常够用。
3.3 SCHED_DEADLINE:最晚完成时间约束
SCHED_DEADLINE 是后来加入的,算是实时调度里的「新贵」。它基于 Earliest Deadline First(EDF) 算法,说白了就是:谁的任务 deadline 最近,谁就先跑。
使用 SCHED_DEADLINE 时,你需要告诉内核三个参数:
- Runtime:任务每次运行需要多少 CPU 时间
- Deadline:任务必须在多长时间内完成
- Period:任务的周期
举个例子:一个视频解码任务,每 33ms 需要处理一帧,处理时间不超过 10ms。那么 Runtime=10ms,Deadline=33ms,Period=33ms。内核会保证这个任务在 deadline 之前完成。
核心区别:FIFO/RR 是基于优先级的「谁重要谁先跑」,DEADLINE 是基于时间的「谁急谁先跑」。前者是定性,后者是定量。
我记得第一次用 SCHED_DEADLINE 时,觉得它简直是神器。但后来发现,它也有自己的脾气。比如,如果你设置的 Runtime 比实际执行时间小,任务就会频繁超时,内核会报错。反过来,如果设得太大,又会浪费 CPU 资源。
注意:SCHED_DEADLINE 的优先级高于任何 SCHED_FIFO 和 SCHED_RR 任务。也就是说,一个 deadline 任务可以抢占所有普通实时任务。这既是优点也是风险——用不好会搞乱整个调度系统。
3.4 三种策略的对比
为了方便你快速对比,我整理了一张表:
| 特性 | SCHED_FIFO | SCHED_RR | SCHED_DEADLINE |
|---|---|---|---|
| 调度依据 | 优先级 + 入队顺序 | 优先级 + 时间片 | 最晚完成时间(Deadline) |
| 时间片 | 无 | 有(默认 100ms) | 由 Runtime 参数决定 |
| 同优先级行为 | 先来先跑,跑完才换 | 轮流跑,时间片用完换人 | 不适用(无优先级概念) |
| 适用场景 | 短小精悍的紧急任务 | 多个同等重要的实时任务 | 有明确时间约束的周期性任务 |
| 风险 | 容易饿死其他任务 | 实时性不如 FIFO 硬 | 参数设置不当会失效 |
3.5 知识体系结构图
下面这张图能帮你理清三种策略的关系和选择逻辑:
从上图可以看出,选择哪种策略其实取决于你的需求:
- 如果任务有明确的 deadline,直接用 SCHED_DEADLINE
- 如果没有 deadline,再看是否有多个同等重要的任务需要公平调度
- 有多个同等重要的,用 SCHED_RR;只有一个关键任务,用 SCHED_FIFO
3.6 实际使用建议
说了这么多理论,最后给点实战建议:
- 能用 SCHED_DEADLINE 就用它。它是最「科学」的调度策略,只要参数设置合理,效果远好于 FIFO 和 RR。
- SCHED_FIFO 适合短任务。比如中断处理、快速响应的事件处理。任务执行时间越短越好,千万别在里面做耗时操作。
- SCHED_RR 适合中等长度的周期性任务。比如传感器数据采集、控制循环。时间片机制能保证公平性。
- 优先级别乱设。实时任务的优先级范围是 1-99,99 最高。我见过有人把所有实时任务都设成 99,结果系统直接崩溃。记住:优先级越高,责任越大。
我的个人习惯:调试实时任务时,先用 chrt 命令在用户态测试。比如 chrt -f 50 ./my_task 可以设置 FIFO 优先级 50。等确认没问题了,再在代码里用 sched_setscheduler 系统调用设置。这样能避免因为调度策略写错导致系统挂掉。
嗯,三种调度策略的原理和区别就讲到这里。记住一句话:没有最好的调度策略,只有最合适的。选对策略,你的实时系统才能跑得稳、跑得快。