欢迎光临
我们一直在努力

SCHED_FIFO、SCHED_RR、SCHED_DEADLINE到底有什么区别?从Linux实时调度器理解任务“谁先运行”

在上一篇文章中,我们讨论了一个实时Linux系统为什么仍然可能出现几十微秒甚至毫秒级延迟。

我们分析了IRQ、SoftIRQ、内核线程、锁竞争、调度延迟以及核心隔离等问题,也提到一个非常关键的事实:

实时系统并不是简单地把CPU给实时任务就结束了。

当多个任务同时处于可运行状态时,CPU究竟应该先执行谁?

一个运动控制任务正在等待CPU,一个传感器采集任务也准备运行,同时网络通信任务产生了新的数据,后台还有普通Linux任务正在执行。

这时候,操作系统必须做出选择。

CPU

┌──────┼──────┐
↓ ↓ ↓
控制任务 采集任务 通信任务
│ │ │
└──────┼──────┘

谁先运行?

这个问题,就是**调度器(Scheduler)**需要解决的问题。

对于普通Linux应用来说,调度器更多关注的是公平性、吞吐量和系统整体响应。

但对于实时系统而言,调度器还必须进一步回答:

  • 哪个任务优先级更高?

  • 高优先级任务什么时候可以抢占低优先级任务?

  • 同优先级任务如何共享CPU?

  • 一个任务必须在什么时间之前完成?

  • 如果多个周期性任务同时到达,应该如何安排?

  • 如何降低任务执行时间的不确定性?

Linux中比较典型的实时调度策略包括:

SCHED_FIFO、SCHED_RR和SCHED_DEADLINE。

它们并不是简单的“三种不同优先级”。

三者实际上代表了不同的实时调度思想:

SCHED_FIFO关注优先级顺序,SCHED_RR关注同优先级任务之间的时间片轮转,而SCHED_DEADLINE进一步关注任务的执行预算、周期和截止时间。

理解这三种调度策略,也就理解了实时Linux调度器最核心的一部分。


一、先理解Linux调度器:CPU到底是怎么决定“下一个运行谁”的?

一个CPU核心同一时刻通常只能真正执行一个线程。

但系统中可能同时存在大量Runnable任务:

Task A
Task B
Task C
Task D
Task E

它们都希望获得CPU。

因此Linux需要维护一套调度机制:

多个Runnable任务

Scheduler

选择下一个任务

CPU运行

什么时候需要重新选择任务?

例如:

一个任务执行完毕。

Task A

执行完成

Scheduler

Task B

或者:

一个高优先级任务突然被唤醒。

Task B正在运行

Task A突然唤醒

A优先级更高

抢占B

A开始运行

也可能是:

当前任务的时间片耗尽。

或者:

当前任务主动阻塞等待某个资源。

所以调度器实际上一直在做一件事情:

在当前时刻,从所有可运行任务中选择最合适的任务交给CPU。

普通Linux中的CFS主要面向普通任务,而实时任务则具有更明确的调度规则。

可以简单理解为:

普通任务

更强调公平

实时任务

更强调优先级和时间约束

因此,当系统中存在实时任务时,调度策略本身就会直接影响实时性。


二、SCHED_FIFO:最经典的实时优先级调度

SCHED_FIFO可以说是Linux中最容易理解的一种实时调度策略。

它的核心思想非常直接:

实时优先级越高,越应该优先获得CPU。

Linux实时调度优先级通常位于一个独立的实时优先级范围。

假设现在有三个实时任务:

任务A:优先级90
任务B:优先级80
任务C:优先级70

那么基本关系就是:

A > B > C

如果A处于Runnable状态:

CPU

A

当A运行时,如果B进入Runnable状态:

A:90 正在运行
B:80 新唤醒

因为:

90 > 80

所以B不会抢占A。

但如果出现:

A:90 正在运行
B:95 新唤醒

那么:

95 > 90

B就可能抢占A。

可以简单表示为:

低优先级任务
████████████

高优先级任务

│唤醒

████████████

抢占低优先级任务

这就是实时Linux中非常经典的优先级抢占。

对于工业控制来说,这种机制非常容易对应到实际任务:

安全保护 Priority 99
运动控制 Priority 90
传感器采集 Priority 80
工业通信 Priority 70
状态监控 Priority 60

当安全保护任务需要CPU时,可以优先运行。

当运动控制任务需要CPU时,可以抢占更低优先级任务。

这种设计非常适合:

任务优先级关系明确的实时系统。

但是,SCHED_FIFO有一个需要特别注意的问题。


三、SCHED_FIFO为什么叫FIFO?它并不是普通意义上的“先进先出”

FIFO的意思是:

First In, First Out。

但在Linux实时调度中,它主要体现在相同实时优先级任务之间的运行顺序。

假设:

Task A:Priority 80
Task B:Priority 80
Task C:Priority 80

A、B、C都是同一优先级。

如果A正在运行:

A
████████████████

B和C随后进入Runnable状态。

在没有更高优先级任务的情况下,SCHED_FIFO不会像普通时间片调度那样自动让A运行一会儿就强制切换。

A可以持续运行,直到:

  • 主动阻塞;

  • 主动让出CPU;

  • 被更高优先级任务抢占。

因此:

A
██████████████████████████

如果A内部出现一个很长的循环,而且没有合理地阻塞或让出CPU,那么B和C可能长时间无法运行。

这就是SCHED_FIFO的一个典型特点:

它非常强调实时优先级,但也要求开发者对任务行为负责。

因此,SCHED_FIFO特别适合那些:

执行逻辑明确、执行时间可控、优先级关系清晰的关键实时任务。

例如:

控制周期任务
实时采集
关键状态监测
安全控制

但如果系统中存在大量同优先级任务,或者无法很好地控制任务执行时间,就需要进一步考虑SCHED_RR。


四、SCHED_RR:同样是实时任务,但让大家“轮流用CPU”

SCHED_RR可以理解成:

实时优先级 + 时间片轮转。

它和SCHED_FIFO一样,也使用实时优先级。

例如:

A:Priority 80
B:Priority 80
C:Priority 80

但是,当它们拥有相同优先级时,SCHED_RR允许这些任务通过时间片轮流获得CPU。

例如:

A
████

B
████

C
████

A
████

这样做的好处是:

避免一个同优先级任务长期占据CPU。

所以可以简单理解:

SCHED_FIFO:

A → A → A → A → A
直到A主动让出CPU

SCHED_RR:

A → B → C → A → B → C
同优先级轮流运行

当然,SCHED_RR也不是简单的“大家平均分CPU”。

它首先仍然遵循实时优先级。

如果:

A:Priority 90
B:Priority 80
C:Priority 80

那么A仍然优先于B和C。

只有在:

B:Priority 80
C:Priority 80

这种同优先级情况下,才体现出RR的时间片轮转。

因此:

SCHED_RR解决的主要是同优先级实时任务之间的CPU共享问题。


五、SCHED_FIFO和SCHED_RR到底应该怎么理解?

把两个策略放在一起,就很容易理解:

调度策略核心思想同优先级任务适合场景
SCHED_FIFO 优先级抢占 先运行的任务可持续运行 关键控制、执行时间可控的任务
SCHED_RR 优先级抢占+时间片 同优先级轮流运行 多个同等级实时任务
SCHED_DEADLINE Deadline/时间约束 根据时间参数调度 周期性、时间约束明确的任务

但需要注意:

不要把这张表理解成“哪个策略更高级”。

三种策略解决的是不同问题。

例如一个简单的电机控制系统:

电机保护
运动控制
传感器采集

任务之间存在明显的优先级关系,那么SCHED_FIFO可能非常自然。

而如果存在多个相同重要程度的实时任务:

采集任务A
采集任务B
通信任务C

SCHED_RR就可以提供更明确的轮转行为。

而如果系统中的任务具有:

周期
执行预算
截止时间

那么就需要进一步理解SCHED_DEADLINE。


六、SCHED_DEADLINE:实时调度从“谁优先”走向“什么时候必须完成”

SCHED_FIFO和SCHED_RR的核心逻辑仍然是:

优先级。

但现实中的很多实时任务,并不是简单地说:

“我比你重要,所以我优先。”

它们更关心:

“我必须在某个时间之前完成。”

例如一个控制任务:

周期:1ms
执行时间:200μs
截止时间:1ms

可以理解成:

|——— 1ms ———|

任务释放

████ 200μs

Deadline

任务每1ms释放一次。

每次大约需要200μs CPU时间。

同时,它需要在1ms的截止时间内完成。

这与简单的“优先级90”是不同的描述方式。

优先级告诉调度器:

我应该排在谁前面。

Deadline模型进一步告诉调度器:

我什么时候需要完成,以及我大概需要多少CPU时间。

Linux的SCHED_DEADLINE基于EDF(Earliest Deadline First,最早截止时间优先)等实时调度思想,并结合运行预算和周期等参数描述任务。

工程上通常会关注三个重要概念:

Runtime
Period
Deadline

可以粗略理解成:

Runtime:

一次周期中最多需要多少CPU执行时间。

Period:

任务多久释放一次。

Deadline:

任务需要在什么时候之前完成。

例如:

Runtime = 200μs
Period = 1ms
Deadline = 1ms

可以表示:

每1ms

释放任务

最多使用200μs CPU

在deadline前完成

这比简单地说:

Priority = 90

包含了更多时间维度的信息。


七、为什么SCHED_DEADLINE特别适合周期性实时任务?

工业控制中有大量周期性任务。

例如:

电机控制:1ms
传感器采集:2ms
状态估计:5ms
轨迹计算:10ms

它们具有明显的周期性。

如果全部用简单优先级描述:

电机控制 90
传感器采集 80
状态估计 70
轨迹计算 60

这种方式当然可以工作。

但优先级本身并没有告诉系统:

任务多久执行一次?
一次执行需要多少CPU?
必须什么时候完成?

而Deadline调度提供了一种更加直接的时间约束表达方式。

例如:

任务A
Runtime = 100μs
Period = 1ms
Deadline = 1ms

任务B
Runtime = 200μs
Period = 5ms
Deadline = 5ms

系统可以根据这些时间参数进行调度。

这对于大量周期性任务组成的实时系统具有很强的工程意义。


八、SCHED_DEADLINE并不意味着“绝对实时”

这里必须强调一个非常重要的问题:

使用SCHED_DEADLINE,不代表任务一定能够在Deadline之前完成。

调度器只能根据任务的时间约束进行资源安排。

如果整个系统已经超载:

CPU可用计算能力

不足

所有任务的Runtime需求

超过CPU能力

那么无论采用什么调度策略,都不可能凭空创造CPU时间。

例如一个单核CPU:

任务A:每1ms需要600μs
任务B:每1ms需要500μs

总需求:

600 + 500 = 1100μs

但CPU每1ms只有:

1000μs

那么系统从资源层面就已经无法满足。

所以:

实时调度的前提,是任务集合本身具备可调度性。

这也是为什么真正的实时系统设计需要同时考虑:

CPU算力
+
任务执行时间
+
任务周期
+
Deadline
+
调度策略
+
任务间资源竞争

而不是只设置一个调度策略。


九、实时优先级越高越好吗?

这是实际项目中非常容易犯的错误。

有工程师会想:

“既然实时任务重要,那我把它设置成最高优先级就好了。”

实际上并不是。

假设:

任务A:Priority 99
任务B:Priority 98
任务C:Priority 97
任务D:Priority 96

如果A的执行时间非常长,那么B、C、D都可能受到影响。

更严重的是,如果A需要等待某个低优先级任务持有的锁,还可能出现优先级反转。

因此,实时系统的优先级设计应该基于:

  • 任务截止时间;

  • 执行周期;

  • 执行时间;

  • 任务重要性;

  • 资源依赖;

  • 数据依赖;

  • 安全要求。

而不是简单地:

“越重要,优先级越高。”

更准确地说:

实时调度是一种系统级资源规划,而不是给任务贴标签。


十、调度策略和核心隔离必须结合起来看

这也是上一篇和这一篇真正应该连接起来的地方。

上一篇我们讲核心隔离:

CPU 0~3
通用任务

CPU 4~5
AI计算

CPU 6~7
实时控制

这一篇我们再进一步:

CPU 6~7

实时任务域

SCHED_FIFO
SCHED_RR
SCHED_DEADLINE

不同任务进一步调度

于是系统形成了两个层次:

第一层:空间隔离

哪些任务进入哪个CPU?

通过:

CPU affinity
cpuset
core isolation
IRQ affinity

解决。

第二层:时间调度

进入实时CPU之后,谁先运行?

通过:

SCHED_FIFO
SCHED_RR
SCHED_DEADLINE

解决。

因此可以把实时系统理解成:

多核CPU

┌───────┴───────┐
↓ ↓
通用计算域 实时计算域
│ │
AI / 网络 / UI 控制 / 采集
│ │
│ ┌──────┼──────┐
│ ↓ ↓ ↓
│ FIFO RR DEADLINE
│ │ │ │
└────────┴──────┴──────┘

CPU执行

这也是现代实时Linux架构中一个非常重要的思想:

空间上隔离,时间上调度。


十一、一个真实的工业控制系统应该如何设计任务调度?

假设现在有一个机器人控制器:

8核CPU

同时运行:

AI视觉
运动控制
传感器采集
EtherCAT
状态估计
日志
Web管理
数据分析

可以考虑形成类似的资源结构:

CPU 0~3
普通Linux
├── Web
├── 日志
├── 文件系统
└── 数据处理

CPU 4~5
AI计算
├── 视觉
└── 推理

CPU 6
实时通信
└── EtherCAT

CPU 7
核心控制
├── 电机控制
├── 位置控制
└── 安全任务

然后在CPU 7内部:

安全任务
SCHED_FIFO
Priority 90

运动控制
SCHED_FIFO
Priority 80

状态任务
SCHED_RR
Priority 70

如果系统存在周期性任务,也可以根据具体负载评估SCHED_DEADLINE。

这样就形成:

AI

通用计算域

控制

实时CPU

实时CPU内部

实时调度策略

这个架构的关键并不是某一个调度策略。

而是:

让任务的实际特性与调度机制匹配。


十二、一个更容易被忽视的问题:调度器之外,还有锁

假设运动控制任务使用:

SCHED_FIFO
Priority 90

它看起来已经拥有非常高的优先级。

但它突然需要访问共享资源:

Shared Resource

Lock

结果锁被Priority 60的任务占用。

那么Priority 90的任务仍然需要等待。

这说明:

调度优先级

实际响应时间

因为任务的执行还受到:


IRQ
SoftIRQ
内存
Cache
驱动

等因素影响。

这也是为什么上一篇我们专门讨论了优先级反转。

实时调度策略只是整个实时系统中的一个环节。

真正的确定性来自:

调度
+
隔离
+
资源管理
+
内核机制
+
驱动
+
硬件

共同作用。


十三、如何在Linux中查看一个任务到底采用了什么调度策略?

在实际调试中,可以通过Linux命令查看任务的调度信息。

例如:

chrt -p <PID>

可以查看指定进程的实时调度策略和优先级。

也可以通过:

ps -eLo pid,tid,cls,rtprio,pri,psr,comm

观察线程:

PID
TID
CLS
RTPRIO
PRI
PSR
COMM

其中:

CLS

可以帮助判断调度类别。

而:

RTPRIO

则可以看到实时优先级。

例如你可能看到:

PID CLS RTPRIO PSR COMM

1201 FF 90 7 motor_control
1202 FF 80 7 sensor_task
1203 RR 70 6 communication
1204 DL – 6 estimator

这样就能够从系统层面确认:

任务到底是不是按照预期运行。


十四、为什么调度策略必须和任务执行时间一起设计?

假设有一个任务:

周期:1ms
执行时间:900μs

即使使用SCHED_FIFO,如果它每1ms占用CPU 900μs:

|———-1ms———-|
██████████████████ |
900μs |

那么留给其他任务的空间已经非常有限。

如果再有:

任务B:200μs

那么:

900 + 200 = 1100μs

单核系统就无法满足。

所以实时系统设计中经常会讨论:

CPU utilization。

简单理解:

所有实时任务执行时间
÷
CPU可用时间

当然,真正的实时可调度性分析比这个简单公式复杂得多,但它至少提醒工程师:

实时任务不是越多越好,实时优先级也不能解决算力不足的问题。

因此,一个好的实时系统设计通常需要在开发阶段就进行:

任务建模

周期分析

执行时间测量

CPU资源评估

调度策略选择

核心隔离

压力测试

最坏延迟验证


十五、从SCHED_FIFO到SCHED_DEADLINE,本质上是实时调度思想的变化

把三种策略放到一起,可以看到Linux实时调度发展的一个重要逻辑:

SCHED_FIFO

关注:

谁优先?

Priority 90

先运行

SCHED_RR

关注:

同样优先级的任务怎么共享CPU?

A → B → C → A → B → C

SCHED_DEADLINE

进一步关注:

任务需要多少CPU时间?什么时候必须完成?

Runtime
Period
Deadline

因此可以把它理解为:

优先级

时间片

时间约束

这并不是说SCHED_DEADLINE“替代”FIFO和RR。

而是说明:

实时任务的调度需求越来越需要从单纯的优先级关系,走向更加完整的时间约束描述。


十六、对于智能制造而言,真正重要的是“确定性调度”

回到我们最开始讨论的AI+制造。

一台智能制造设备可能同时存在:

AI视觉
AI推理
工业通信
实时采集
运动控制
状态估计
设备管理

AI任务可能需要高吞吐量。

工业通信可能需要稳定周期。

运动控制可能要求严格的时间窗口。

设备管理则可能只需要普通Linux调度。

这意味着:

不同任务天然具有不同的时间特性。

如果所有任务都按照完全相同的方式调度:

AI
通信
控制
日志
Web
全部共享

系统设计会变得非常复杂。

而通过:

核心隔离
+
实时调度
+
任务优先级
+
Deadline约束

可以进一步建立更加明确的计算资源边界。

这也是实时操作系统在智能制造中的重要价值。

不是让所有任务都变成实时任务,而是让真正需要实时性的任务获得确定性的计算资源。


十七、望获OS真正需要解决的,也是“确定性”而不仅仅是“实时Linux”

从整个实时Linux技术链路来看,单纯讨论某一个调度策略其实是不够的。

因为最终的实时性能取决于:

硬件

CPU多核架构

核心隔离

IRQ隔离

调度策略



内存

驱动

应用

实时测试

任何一个环节出现不可控因素,都可能影响最终的最坏延迟。

因此,真正面向工业控制的实时操作系统,需要关注的并不是:

“有没有SCHED_FIFO?”

也不是:

“有没有PREEMPT_RT?”

而是:

能不能围绕实时任务建立一套完整、可验证、可预测的执行环境。

这也是望获OS实时技术路线值得持续展开的方向。

围绕工业控制、机器人、实时仿真、智能制造等应用场景,实时操作系统不仅需要提供实时调度能力,还需要进一步考虑:

核心隔离、任务隔离、IRQ隔离、资源管理以及系统级确定性。

只有把这些能力组合起来,实时操作系统才能真正从“降低平均延迟”走向:

控制最坏延迟。


结语:实时调度真正解决的,是“关键任务什么时候必须运行”

回到文章开头的问题:

SCHED_FIFO、SCHED_RR和SCHED_DEADLINE到底有什么区别?

可以用一句话概括:

SCHED_FIFO强调实时优先级和先来先运行,SCHED_RR在此基础上解决同优先级任务的时间片轮转,而SCHED_DEADLINE则进一步从周期、运行预算和截止时间的角度描述实时任务。

但对于实际工程而言,更重要的不是记住三个名字,而是理解背后的设计思想:

核心隔离
解决:
“任务在哪里运行?”

实时调度
解决:
“任务什么时候运行?”

锁与资源管理
解决:
“任务为什么可能无法运行?”

实时测试
解决:
“系统实际上表现怎么样?”

最终,实时系统真正追求的不是:

让CPU永远运行最高优先级任务。

而是:

让每一个关键任务,都能够在系统设计规定的时间窗口内获得足够的计算资源,并且尽可能降低不可预测的干扰。

这才是实时Linux调度器真正的意义。

当AI开始大规模进入工业现场之后,这种确定性的重要性还会进一步提升。

因为未来的智能设备很可能同时需要:

AI的智能决策
+
实时系统的确定性执行

AI可以告诉设备:

“下一步应该做什么。”

而实时调度器需要确保:

“关键动作能够在应该发生的时间发生。”

这两者共同构成了下一代智能制造系统的底层软件基础。

而下一篇,我们可以继续深入一个实时系统中非常经典、也非常容易踩坑的问题:

《实时Linux为什么会出现优先级反转?从Mutex、Priority Inheritance到实时锁机制详解》

这一篇将直接从一个真实的“高优先级任务反而被低优先级任务拖住”的案例开始,继续把调度器 → 锁 → 优先级反转 → Priority Inheritance → 核心隔离这条技术链补完整。

#实时Linux #SCHED_FIFO #SCHED_RR #SCHED_DEADLINE #Linux调度器 #实时操作系统 #工业控制 #智能制造 #机器人 #核心隔离 #嵌入式Linux

赞(0)
未经允许不得转载:171主机测评 » SCHED_FIFO、SCHED_RR、SCHED_DEADLINE到底有什么区别?从Linux实时调度器理解任务“谁先运行”
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址