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 知识体系结构图

下面这张图能帮你理清三种策略的关系和选择逻辑:

实时调度策略选择逻辑 实时任务 有明确时间约束? 无明确时间约束 SCHED_DEADLINE 多个同等重要? SCHED_RR SCHED_FIFO 决策节点 最终策略

从上图可以看出,选择哪种策略其实取决于你的需求:

  • 如果任务有明确的 deadline,直接用 SCHED_DEADLINE
  • 如果没有 deadline,再看是否有多个同等重要的任务需要公平调度
  • 有多个同等重要的,用 SCHED_RR;只有一个关键任务,用 SCHED_FIFO

3.6 实际使用建议

说了这么多理论,最后给点实战建议:

  1. 能用 SCHED_DEADLINE 就用它。它是最「科学」的调度策略,只要参数设置合理,效果远好于 FIFO 和 RR。
  2. SCHED_FIFO 适合短任务。比如中断处理、快速响应的事件处理。任务执行时间越短越好,千万别在里面做耗时操作。
  3. SCHED_RR 适合中等长度的周期性任务。比如传感器数据采集、控制循环。时间片机制能保证公平性。
  4. 优先级别乱设。实时任务的优先级范围是 1-99,99 最高。我见过有人把所有实时任务都设成 99,结果系统直接崩溃。记住:优先级越高,责任越大。

我的个人习惯:调试实时任务时,先用 chrt 命令在用户态测试。比如 chrt -f 50 ./my_task 可以设置 FIFO 优先级 50。等确认没问题了,再在代码里用 sched_setscheduler 系统调用设置。这样能避免因为调度策略写错导致系统挂掉。

嗯,三种调度策略的原理和区别就讲到这里。记住一句话:没有最好的调度策略,只有最合适的。选对策略,你的实时系统才能跑得稳、跑得快。


专注资料整理