OS调度配置与任务优先级深度调优 - 第3节

实时调度策略:SCHED_FIFO与SCHED_RR的核心差异

实时调度,说白了就是给关键任务开「绿色通道」。

我刚开始接触Linux实时调度时,有个很深的体会:普通进程调度像公交车,按站停靠、人人有份;实时调度则像救护车,红灯也得让路。这两种调度策略——SCHED_FIFO和SCHED_RR,就是实时调度的两种「救护车模式」。

SCHED_FIFO:先来先服务,不打断

FIFO的全称是First In First Out。它的规则很简单:

  • 一个实时任务一旦获得CPU,就会一直运行,直到它主动放弃
  • 放弃的条件有三个:任务自己休眠、被更高优先级的任务抢占、或者任务主动调用sched_yield()
  • 同优先级的FIFO任务之间,不会互相抢占

我在项目中遇到过这样一个场景:一个数据采集线程用了SCHED_FIFO,优先级设成了80。结果它一旦开始采集,就死死占着CPU不放。另一个同样优先级的日志线程,愣是等了整整2毫秒才轮到。嗯,2毫秒在实时系统里,已经够一个控制周期跑两轮了。

核心特点:SCHED_FIFO适合那些「要么不做,要做就一口气做完」的任务。比如中断处理的下半部、短促的硬件控制指令。

SCHED_RR:轮流坐庄,时间片轮转

SCHED_RR和FIFO很像,但多了一个时间片机制。

你想想看,如果两个同优先级的实时任务都用FIFO,第二个任务可能永远得不到CPU——除非第一个主动让出。这太危险了。RR就是来解决这个问题的。

  • 每个RR任务有一个默认的时间片(通常是100ms,可以调)
  • 时间片用完后,自动切换到同优先级的下一个RR任务
  • 如果被更高优先级任务抢占,回来后会继续消耗剩余的时间片

我个人习惯把RR用在「多个同等重要的周期性任务」上。比如三个传感器数据采集任务,优先级都是50,用RR调度,每个轮流跑100ms,公平又可控。

特性 SCHED_FIFO SCHED_RR
同优先级抢占 不抢占,直到主动让出 时间片用完自动切换
适用场景 短促、不可中断的操作 多个同等重要的周期性任务
风险 同优先级任务可能饿死 时间片设置不当导致抖动

实时优先级范围:1-99的玄机

Linux的实时优先级范围是1到99。数字越大,优先级越高。99是最高,1是最低。

这里有个容易踩的坑:普通进程的nice值是-20到19,和实时优先级完全是两套体系。实时优先级1-99永远高于任何普通进程。换句话说,只要有一个实时任务在跑,普通进程就得靠边站。

我曾经犯过一个错误:把某个后台监控任务的实时优先级设成了99。结果这个监控任务里有个死循环,整个系统的普通进程全部卡死,连ssh都连不上去。最后只能按电源键重启。

优先级分配有个基本原则:

  • 99-90:留给最核心的中断处理、时钟同步等
  • 89-50:留给关键控制回路、数据采集
  • 49-1:留给辅助性的实时任务,比如日志、状态上报

我个人建议,除非你非常确定,否则不要轻易用90以上的优先级。留点余量,给未来可能出现的更紧急任务。

实时任务的设计约束与风险

实时调度不是银弹。用不好,系统反而更不稳定。我总结了几个必须注意的约束:

约束一:不能长时间占用CPU

实时任务如果一直跑,普通进程就永远没机会。更严重的是,内核的一些关键守护进程(比如kswapd、pdflush)也是普通优先级。如果实时任务占着CPU不放,内存回收、磁盘写入都会停摆。

解决办法:实时任务里不要做耗时操作。比如文件读写、网络请求、动态内存分配,这些都应该避免。

约束二:优先级反转

这是一个经典问题。假设任务A(优先级80)和任务B(优先级70)共享一个锁。A先拿到锁,然后被更高优先级的C(优先级90)抢占。C也需要这个锁,但锁在A手里。A又被C抢占着,没法释放锁。结果C等着A,A等着C——死锁了。

Linux内核提供了优先级继承协议来解决这个问题。但前提是你得用pthread_mutexattr_setprotocol()正确配置互斥锁。

避坑指南:我曾经在一个无人机飞控项目里遇到过优先级反转。现象是:偶尔出现电机响应延迟几百毫秒。排查了两天才发现,是一个低优先级的日志任务占着I2C总线锁不放。后来给所有共享资源加了优先级继承,问题就消失了。

约束三:系统调用开销

实时任务进入内核态(比如调用read、write)时,调度行为会变得复杂。内核里有很多自旋锁、中断屏蔽区,这些都会影响实时性。

我建议:实时任务尽量用vDSO、io_uring这些低开销的机制。实在不行,就用内存映射(mmap)来避免系统调用。

知识体系结构图

下面这张图,把实时调度的核心逻辑串起来了:

实时调度核心知识体系 实时调度策略 SCHED_FIFO SCHED_RR 先来先服务,不被打断 同优先级任务可能饿死 时间片轮转,公平调度 时间片设置影响抖动 优先级范围:1(低)→ 99(高) 约束:避免长时间占用 | 防止优先级反转 | 减少系统调用

总结一下

实时调度不是玄学,是工程。选FIFO还是RR,设优先级多少,都得根据任务的实际行为来定。

我最后想强调一点:实时调度是把双刃剑。用好了,系统响应如丝般顺滑;用不好,系统崩溃就在一瞬间。每次上线前,我都会做一次「实时任务审查」——检查每个实时任务的执行路径、持有锁的情况、最大执行时间。这个习惯救了我好几次。

一个小技巧:在开发阶段,可以用chrt命令临时调整进程的调度策略和优先级来测试。比如:chrt -f 50 ./my_realtime_app 就把my_realtime_app设成了FIFO策略、优先级50。


可可精品资料库